├── .gitattributes ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── PartitioningAgent.Test ├── AgentTest.cs ├── PartitioningAgent.Test.csproj └── helpers │ ├── Constants.cs │ ├── SomeException.cs │ └── TaskExtensions.cs ├── PartitioningAgent ├── Agent.cs └── PartitioningAgent.csproj ├── README.md ├── Services.Test ├── AzureManagementAdapterTest.cs ├── Clustering │ ├── ClusterNodesTest.cs │ ├── ClusteringConfigTest.cs │ └── DevicePartitionsTest.cs ├── Concurrency │ ├── LoopSettingsTest.cs │ ├── PerMinuteCounterTest.cs │ ├── PerSecondCounterTest.cs │ └── RatedCounterTest.cs ├── CustomDeviceModelsTest.cs ├── DataStructures │ ├── InstanceTest.cs │ └── ListExtensionsTest.cs ├── DeviceClientTest.cs ├── DeviceModelScriptsTest.cs ├── DeviceModelsGenerationTest.cs ├── DeviceModelsTest.cs ├── DevicePropertiesTest.cs ├── DevicesTest.cs ├── Diagnostics │ └── DiagnosticsLoggerTest.cs ├── IntegrationTests │ └── README.md ├── IotHub │ ├── ConnectionStringValidationTest.cs │ └── ConnectionStringsTest.cs ├── IotHubMetricsTest.cs ├── Models │ ├── DeviceModelTest.cs │ └── SimulationTest.cs ├── README.md ├── ReplayFileServiceTest.cs ├── Runtime │ └── ConfigDataTest.cs ├── Services.Test.csproj ├── Simulation │ └── JavascriptInterpreterTest.cs ├── SimulationsTest.cs ├── SmartDictionaryTest.cs ├── Statistics │ └── SimulationStatisticsTest.cs ├── StockDeviceModelTest.cs ├── Storage │ ├── ConfigTest.cs │ ├── CosmosDbSql │ │ ├── DataRecordTest.cs │ │ └── EngineTest.cs │ ├── EnginesTest.cs │ └── TableStorage │ │ ├── DataRecordTest.cs │ │ └── EngineTest.cs └── helpers │ ├── Constants.cs │ ├── SomeException.cs │ ├── TargetLogger.cs │ └── TaskExtensions.cs ├── Services ├── AzureManagementAdapter │ ├── AadTokenModel.cs │ ├── AutoScaleSettingsModel.cs │ ├── AzureManagementAdapter.cs │ └── MetricsModel.cs ├── Clustering │ ├── ClusterNodes.cs │ ├── ClusteringConfig.cs │ └── DevicePartitions.cs ├── Concurrency │ ├── ConcurrencyConfig.cs │ ├── ETags.cs │ ├── LoopSettings.cs │ ├── RateLimiting.cs │ ├── RateLimitingConfig.cs │ ├── RatedCounter.cs │ ├── ThreadWrapper.cs │ └── Timer.cs ├── CustomDeviceModels.cs ├── DataStructures │ ├── Instance.cs │ ├── ListExtensions.cs │ └── SmartDictionary.cs ├── DeviceClient.cs ├── DeviceMethods.cs ├── DeviceModelScripts.cs ├── DeviceModels.cs ├── DeviceModelsGeneration.cs ├── DeviceProperties.cs ├── Devices.cs ├── Diagnostics │ ├── ActorsLogger.cs │ ├── ActorsLoggerShim.cs │ ├── DiagnosticsLogger.cs │ ├── IActorsLogger.cs │ ├── ILogger.cs │ ├── Logger.cs │ ├── LoggingConfig.cs │ └── Serialization.cs ├── Exceptions │ ├── BrokenDeviceClientException.cs │ ├── ConflictingResourceException.cs │ ├── CustomException.cs │ ├── DailyTelemetryQuotaExceededException.cs │ ├── DeviceAuthFailedException.cs │ ├── ExternalDependencyException.cs │ ├── InvalidConfigurationException.cs │ ├── InvalidInputException.cs │ ├── InvalidIotHubConnectionStringFormatException.cs │ ├── IotHubConnectionException.cs │ ├── PropertySendException.cs │ ├── ResourceIsLockedByAnotherOwnerException.cs │ ├── ResourceNotFoundException.cs │ ├── ResourceOutOfDateException.cs │ ├── TelemetrySendException.cs │ ├── TelemetrySendIOException.cs │ ├── TelemetrySendTimeoutException.cs │ ├── TotalDeviceCountQuotaExceededException.cs │ └── UnknownMessageFormatException.cs ├── FileWrapper.cs ├── Http │ ├── HttpClient.cs │ ├── HttpRequest.cs │ ├── HttpRequestOptions.cs │ └── HttpResponse.cs ├── IotHub │ ├── ConnectionStringValidation.cs │ ├── ConnectionStrings.cs │ ├── DeviceClientWrapper.cs │ ├── IothubMetrics.cs │ ├── RegistryManagerWrapper.cs │ └── ServiceClientWrapper.cs ├── Models │ ├── DataFile.cs │ ├── Device.cs │ ├── DeviceModel.cs │ ├── DevicesPartition.cs │ ├── IoTHubProtocol.cs │ ├── Protobuf │ │ ├── Truck.cs │ │ └── proto │ │ │ └── Truck.proto │ ├── README.md │ ├── Simulation.cs │ ├── SimulationPatch.cs │ └── SimulationStatisticsModel.cs ├── PreprovisionedIotHub.cs ├── ReplayFileService.cs ├── Runtime │ ├── ConfigData.cs │ ├── DeploymentConfig.cs │ ├── Factory.cs │ └── ServicesConfig.cs ├── Services.csproj ├── Simulation │ ├── InternalInterpreter.cs │ ├── JavascriptInterpreter.cs │ └── ScriptInterpreter.cs ├── Simulations.cs ├── Statistics │ ├── SimulationStatistics.cs │ └── SimulationStatisticsRecord.cs ├── StockDeviceModels.cs ├── Storage │ ├── Config.cs │ ├── CosmosDbSql │ │ ├── DataRecord.cs │ │ ├── Engine.cs │ │ └── SDKWrapper.cs │ ├── Engines.cs │ ├── IDataRecord.cs │ ├── IEngine.cs │ ├── TableStorage │ │ ├── DataRecord.cs │ │ ├── Engine.cs │ │ └── SDKWrapper.cs │ └── Type.cs ├── StorageAdapter │ ├── StorageAdapterClient.cs │ ├── ValueApiModel.cs │ └── ValueListApiModel.cs ├── Utilities.cs └── data │ ├── devicemodels │ ├── README.md │ ├── chiller-01.json │ ├── chiller-02.json │ ├── delivery-truck-01.json │ ├── delivery-truck-02.json │ ├── elevator-01.json │ ├── elevator-02.json │ ├── engine-01.json │ ├── engine-02.json │ ├── prototype-01.json │ ├── prototype-02.json │ ├── scripts │ │ ├── EmergencyValveRelease-method.js │ │ ├── EmptyTank-method.js │ │ ├── FillTank-method.js │ │ ├── FirmwareUpdate-method.js │ │ ├── IncreasePressure-method.js │ │ ├── README.md │ │ ├── Reboot-method.js │ │ ├── SetFuelLevel-method.js │ │ ├── SetTemperature-method.js │ │ ├── StartElevator-method.js │ │ ├── StartMovingDevice-method.js │ │ ├── StopElevator-method.js │ │ ├── StopMovingDevice-method.js │ │ ├── TempDecrease-method.js │ │ ├── TempIncrease-method.js │ │ ├── chiller-01-state.js │ │ ├── chiller-02-state.js │ │ ├── delivery-truck-01-state.js │ │ ├── delivery-truck-02-state.js │ │ ├── elevator-01-state.js │ │ ├── elevator-02-state.js │ │ ├── engine-01-state.js │ │ ├── engine-02-state.js │ │ ├── prototype-01-state.js │ │ ├── prototype-02-state.js │ │ ├── truck-01-state.js │ │ └── truck-02-state.js │ ├── truck-01-protobuf.json │ ├── truck-01.json │ └── truck-02.json │ ├── iothub │ └── README.md │ ├── replayfile │ ├── replay.json │ └── simulationReplayTest.csv │ └── templates │ ├── multiple-simulations-template.json │ └── remote-monitoring-simulations-template.json ├── SimulationAgent.Test ├── DeviceConnection │ ├── ConnectTest.cs │ ├── DeregisterTest.cs │ ├── DeviceConnectionActorTest.cs │ └── DisconnectTest.cs ├── DeviceProperties │ ├── DevicePropertiesActorTest.cs │ ├── SetDeviceTagTest.cs │ └── UpdateReportedPropertiesTest.cs ├── DeviceState │ └── DeviceStateActorTest.cs ├── DeviceTelemetry │ └── DeviceTelemetryActorTest.cs ├── SimulationAgent.Test.csproj ├── SimulationManagerTest.cs ├── SimulationThreads │ ├── DeviceConnectionTaskTest.cs │ ├── DeviceReplayTaskTest.cs │ ├── DeviceStateTaskTest.cs │ ├── DeviceTelemetryTaskTest.cs │ └── UpdatePropertiesTaskTest.cs └── helpers │ ├── Constants.cs │ ├── Http │ ├── HttpClient.cs │ ├── HttpRequest.cs │ ├── HttpRequestOptions.cs │ └── HttpResponse.cs │ └── TaskExtensions.cs ├── SimulationAgent ├── Agent.cs ├── DeviceConnection │ ├── Connect.cs │ ├── CredentialsSetup.cs │ ├── DeRegister.cs │ ├── DeviceConnectionActor.cs │ ├── Disconnect.cs │ ├── FetchFromRegistry.cs │ ├── IDeviceConnectionLogic.cs │ └── Register.cs ├── DeviceProperties │ ├── DevicePropertiesActor.cs │ ├── IDevicePropertiesLogic.cs │ ├── SetDeviceTag.cs │ └── UpdateReportedProperties.cs ├── DeviceReplay │ └── DeviceReplayActor.cs ├── DeviceState │ ├── DeviceStateActor.cs │ └── UpdateDeviceState.cs ├── DeviceTelemetry │ ├── DeviceTelemetryActor.cs │ ├── IDeviceTelemetryLogic.cs │ └── SendTelemetry.cs ├── SimulationAgent.csproj ├── SimulationContext.cs ├── SimulationManager.cs └── SimulationThreads │ ├── DeviceConnectionTask.cs │ ├── DeviceReplayTask.cs │ ├── DeviceStateTask.cs │ ├── DeviceTelemetryTask.cs │ └── UpdatePropertiesTask.cs ├── WebService.Test ├── IntegrationTests │ └── README.md ├── README.md ├── WebService.Test.csproj ├── helpers │ ├── Constants.cs │ ├── Http │ │ ├── HttpClient.cs │ │ ├── HttpRequest.cs │ │ ├── HttpRequestOptions.cs │ │ └── HttpResponse.cs │ ├── SomeException.cs │ ├── TaskExtensions.cs │ └── WebServiceHost.cs └── v1 │ ├── Controllers │ ├── DeviceModelPropertiesControllerTest.cs │ ├── DeviceModelScriptsControllerTest.cs │ ├── DeviceModelsControllerTest.cs │ ├── SimulationsControllerTest.cs │ └── StatusControllerTest.cs │ └── Models │ ├── DeviceModelApiModel │ ├── DeviceModelApiModelTest.cs │ ├── DeviceModelSimulationScriptTest.cs │ ├── DeviceModelSimulationTest.cs │ ├── DeviceModelTelemetrySchemaTest.cs │ └── DeviceModelTelemetryTest.cs │ ├── Helpers │ └── DateHelperTest.cs │ └── SimulationApiModel │ ├── DeviceModelApiModelOverrideTest.cs │ ├── DeviceModelSimulationOverrideTest.cs │ ├── DeviceModelSimulationScriptOverrideTest.cs │ ├── DeviceModelTelemetryMessageSchemaOverrideTest.cs │ ├── DeviceModelTelemetryOverrideTest.cs │ ├── MetricsApiModelTest.cs │ ├── SimulationApiModelTest.cs │ └── SimulationDeviceModelRefTest.cs ├── WebService ├── Auth │ ├── AuthMiddleware.cs │ ├── ClientAuthConfig.cs │ ├── CorsSetup.cs │ ├── CorsWhitelistModel.cs │ ├── RequestExtension.cs │ └── Startup.cs ├── DependencyResolution.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── README.md ├── Runtime │ ├── Config.cs │ ├── ConfigFile.cs │ └── Uptime.cs ├── Startup.cs ├── WebService.csproj ├── appsettings.ini └── v1 │ ├── Controllers │ ├── DeviceModelPropertiesController.cs │ ├── DeviceModelScriptsController.cs │ ├── DeviceModelsController.cs │ ├── README.md │ ├── ReplayFileController.cs │ ├── SimulationsController.cs │ └── StatusController.cs │ ├── Exceptions │ ├── BadRequestException.cs │ ├── InvalidDateFormatException.cs │ ├── InvalidIntervalException.cs │ └── InvalidIotHubConnectionStringFormatException.cs │ ├── Filters │ └── ExceptionsFilterAttribute.cs │ ├── Models │ ├── DeviceModelApiModel │ │ ├── DeviceModelApiModel.cs │ │ ├── DeviceModelScript.cs │ │ ├── DeviceModelSimulation.cs │ │ ├── DeviceModelTelemetry.cs │ │ └── DeviceModelTelemetryMessageSchema.cs │ ├── DeviceModelListApiModel.cs │ ├── DeviceModelPropertyListApiModel.cs │ ├── DeviceModelScriptApiModel │ │ └── DeviceModelScriptApiModel.cs │ ├── DeviceModelScriptListModel.cs │ ├── Devices │ │ ├── BatchDeleteActionApiModel.cs │ │ └── CreateActionApiModel.cs │ ├── Helpers │ │ ├── DateHelper.cs │ │ ├── IntervalHelper.cs │ │ └── ValidationApiModel.cs │ ├── IoTHubApiModel.cs │ ├── README.md │ ├── ReplayFileApiModel.cs │ ├── SimulationApiModel │ │ ├── DeviceModelApiModelOverride.cs │ │ ├── DeviceModelSimulationOverride.cs │ │ ├── DeviceModelSimulationScriptOverride.cs │ │ ├── DeviceModelTelemetryMessageSchemaOverride.cs │ │ ├── DeviceModelTelemetryOverride.cs │ │ ├── MetricsApiModel.cs │ │ ├── SimulationApiModel.cs │ │ ├── SimulationDeviceModelRef.cs │ │ ├── SimulationIotHub.cs │ │ ├── SimulationRateLimits.cs │ │ └── SimulationStatistics.cs │ ├── SimulationListApiModel.cs │ ├── SimulationPatchApiModel.cs │ └── StatusApiModel.cs │ └── Version.cs ├── device-simulation.sln ├── device-simulation.sln.DotSettings ├── docs ├── API_SPECS_DEVICE_MODELS.md ├── API_SPECS_DEVICE_MODEL_PROPERTIES.md ├── API_SPECS_DEVICE_MODEL_SCRIPTS.md ├── API_SPECS_SERVICE.md ├── API_SPECS_SIMULATIONS.md ├── BREAKING_CHANGES.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVICE_MODELS.md ├── ENVIRONMENT_VARIABLES.md ├── INDEX.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── overview.png └── postman │ ├── Azure IoT Device Simulation solution accelerator.postman_collection.json │ └── Azure IoT Device Simulation solution accelerator.postman_environment.json ├── global.json └── scripts ├── .functions.sh ├── build ├── build.cmd ├── clean-up ├── clean-up.cmd ├── compile ├── compile.cmd ├── development ├── create-simulation.sh ├── delete-simulation.sh ├── postman_collection.json └── start-storage-adapter.sh ├── docker ├── .dockerignore ├── Dockerfile ├── build ├── build.cmd ├── content │ └── run.sh ├── docker-compose.yml ├── publish ├── publish.cmd ├── run └── run.cmd ├── env-vars-check ├── env-vars-check.cmd ├── env-vars-setup ├── env-vars-setup.cmd ├── git ├── .functions.sh ├── fix-perms.sh ├── pre-commit-runner-no-sandbox.sh ├── pre-commit-runner-with-sandbox.sh ├── pre-commit.sh ├── setup └── setup.cmd ├── run ├── run.cmd ├── travis └── travis.cmd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft. All rights reserved. 2 | 3 | # Auto-detect text files, ensure they use LF. 4 | * text=auto eol=lf 5 | 6 | # Bash scripts 7 | *.sh text eol=lf 8 | scripts/build text eol=lf 9 | scripts/clean-up text eol=lf 10 | scripts/compile text eol=lf 11 | scripts/env-vars-check text eol=lf 12 | scripts/env-vars-setup text eol=lf 13 | scripts/run text eol=lf 14 | scripts/travis text eol=lf 15 | scripts/docker/build text eol=lf 16 | scripts/docker/publish text eol=lf 17 | scripts/docker/run text eol=lf 18 | scripts/git/setup text eol=lf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | mono: none 3 | dotnet: 3.1 4 | sudo: false 5 | cache: 6 | directories: 7 | - "$HOME/.nuget/" 8 | before_install: 9 | - set -e 10 | addons: 11 | apt: 12 | packages: 13 | # This package is used only to address an issue in Travis CI, which would 14 | # otherwise install a newer 2.1.x preview, which breaks the build. 15 | script: 16 | - "bash ./$CODEBASE/scripts/build" 17 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "dotnet", 4 | "isShellCommand": true, 5 | "args": [], 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "args": [ 10 | "${workspaceRoot}/WebService/WebService.csproj" 11 | ], 12 | "isBuildCommand": true, 13 | "problemMatcher": "$msCompile" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /PartitioningAgent.Test/PartitioningAgent.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.1 4 | latest 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /PartitioningAgent.Test/helpers/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace PartitioningAgent.Test.helpers 4 | { 5 | public static class Constants 6 | { 7 | // Use this to kill unit tests not supposed to run async code 8 | public const int TEST_TIMEOUT = 5000; 9 | 10 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 11 | public const string TYPE = "Type"; 12 | public const string UNIT_TEST = "UnitTest"; 13 | public const string INTEGRATION_TEST = "IntegrationTest"; 14 | 15 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 16 | public const string SPEED = "Speed"; 17 | public const string SLOW_TEST = "SlowTest"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PartitioningAgent.Test/helpers/SomeException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace PartitioningAgent.Test.helpers 6 | { 7 | /* 8 | This class is used to inject exceptions in the code under test and to verify that 9 | the system fails with the injected exception, i.e. not ANY exception. 10 | This is to avoid false negatives, i.e. to avoid that a test passes due to 11 | an unexpected exception. 12 | 13 | Example usage: 14 | 15 | this.dependencyMock.Setup(x => x.SomeMethod()).Throws(); 16 | 17 | Assert.ThrowsAsync(() => this.target.MethodUnderTest()) 18 | */ 19 | public class SomeException : Exception 20 | { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PartitioningAgent.Test/helpers/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Xunit.Sdk; 5 | 6 | namespace PartitioningAgent.Test.helpers 7 | { 8 | /** 9 | * Use this class when testing asynchronous code, to avoid tests 10 | * running forever, e.g. in case threads don't end as expected. 11 | * 12 | * Example: 13 | * 14 | * this.target.SomeMethodAsync().CompleteOrTimeout(); 15 | * 16 | * var result = this.target.SomeMethodAsync().CompleteOrTimeout().Result; 17 | */ 18 | public static class TaskExtensions 19 | { 20 | // Wait for the task to complete or timeout 21 | public static Task CompleteOrTimeout(this Task t) 22 | { 23 | var complete = t.Wait(Constants.TEST_TIMEOUT); 24 | if (!complete) 25 | { 26 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 27 | } 28 | 29 | return t; 30 | } 31 | 32 | // Wait for the task to complete or timeout 33 | public static Task CompleteOrTimeout(this Task t) 34 | { 35 | var complete = t.Wait(Constants.TEST_TIMEOUT); 36 | if (!complete) 37 | { 38 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 39 | } 40 | 41 | return t; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PartitioningAgent/PartitioningAgent.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | Microsoft.Azure.IoTSolutions.DeviceSimulation.PartitioningAgent 6 | Microsoft.Azure.IoTSolutions.DeviceSimulation.PartitioningAgent 7 | latest 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Services.Test/Concurrency/LoopSettingsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency; 4 | using Moq; 5 | using Services.Test.helpers; 6 | using Xunit; 7 | using Xunit.Abstractions; 8 | 9 | namespace Services.Test.Concurrency 10 | { 11 | public class LoopSettingsTest 12 | { 13 | private readonly ITestOutputHelper log; 14 | private readonly Mock rateLimitingConfig; 15 | private readonly PropertiesLoopSettings propertiesTarget; 16 | 17 | private const int TWIN_WRITES_PER_SECOND = 10; 18 | 19 | public LoopSettingsTest(ITestOutputHelper logger) 20 | { 21 | this.log = logger; 22 | this.rateLimitingConfig = new Mock(); 23 | this.propertiesTarget = new PropertiesLoopSettings(this.rateLimitingConfig.Object); 24 | } 25 | 26 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 27 | public void Should_SetTaggingLimit_When_NewLoopCreated() 28 | { 29 | // Arrange 30 | this.SetupRateLimitingConfig(); 31 | 32 | // Act 33 | this.propertiesTarget.NewLoop(); 34 | 35 | // Assert 36 | // In order for other threads to be able to schedule twin opertations, 37 | // value should be at least 1 but less than the limit per second. 38 | Assert.True(this.propertiesTarget.SchedulableTaggings >= 1); 39 | Assert.True(this.propertiesTarget.SchedulableTaggings < TWIN_WRITES_PER_SECOND); 40 | } 41 | 42 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 43 | public void Should_UseTwinWrites_When_NewLoopCreated() 44 | { 45 | // Arrange 46 | this.SetupRateLimitingConfig(); 47 | 48 | // Act 49 | this.propertiesTarget.NewLoop(); 50 | 51 | // Assert 52 | // ensure twin writes were accessed and no other 53 | // config values to calculate properties limits 54 | this.rateLimitingConfig.VerifyGet(x => x.TwinWritesPerSecond); 55 | this.rateLimitingConfig.VerifyNoOtherCalls(); 56 | } 57 | 58 | private void SetupRateLimitingConfig() 59 | { 60 | this.rateLimitingConfig.Setup(x => x.TwinWritesPerSecond).Returns(TWIN_WRITES_PER_SECOND); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Services.Test/Concurrency/RatedCounterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions; 6 | using Moq; 7 | using Services.Test.helpers; 8 | using Xunit; 9 | 10 | namespace Services.Test.Concurrency 11 | { 12 | public class RatedCounterTest 13 | { 14 | /** 15 | * Checks the configuration validation 16 | * Also covers https://github.com/Azure/device-simulation-dotnet/issues/122 17 | */ 18 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 19 | public void ItDoesNotAllowLowCounterRate() 20 | { 21 | Assert.Throws(() => new BadCounter1(new Mock().Object)); 22 | Assert.Throws(() => new BadCounter2(new Mock().Object)); 23 | } 24 | 25 | class BadCounter1 : RatedCounter 26 | { 27 | public BadCounter1(ILogger logger) 28 | : base(1, 999, "BadCounter1", logger) 29 | { 30 | } 31 | } 32 | 33 | class BadCounter2 : RatedCounter 34 | { 35 | public BadCounter2(ILogger logger) 36 | : base(0, 99999, "BadCounter2", logger) 37 | { 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Services.Test/DataStructures/InstanceTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.DataStructures; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; 6 | using Moq; 7 | using Xunit; 8 | 9 | namespace Services.Test.DataStructures 10 | { 11 | public class InstanceTest 12 | { 13 | private Instance target; 14 | private readonly Mock mockLogger; 15 | 16 | public InstanceTest() 17 | { 18 | this.mockLogger = new Mock(); 19 | this.target = new Instance(this.mockLogger.Object); 20 | } 21 | 22 | [Fact] 23 | public void ItThrowsIfInitOnceIsCalledAfterItIsInitialized() 24 | { 25 | // Arrange 26 | this.target = new Instance(this.mockLogger.Object); 27 | this.target.InitOnce(); 28 | this.target.InitComplete(); 29 | 30 | // Act, Assert 31 | Assert.Throws( 32 | () => this.target.InitOnce()); 33 | } 34 | 35 | [Fact] 36 | public void ItDoesNotThrowIfInitOnceIsCalledBeforeItIsInitialized() 37 | { 38 | // Arrange 39 | this.target = new Instance(this.mockLogger.Object); 40 | 41 | // Act 42 | this.target.InitOnce(); 43 | } 44 | 45 | [Fact] 46 | public void ItThrowsIfInitRequiredIsCalledBeforeInitialization() 47 | { 48 | // Arrange 49 | this.target = new Instance(this.mockLogger.Object); 50 | 51 | // Act, Assert 52 | Assert.Throws( 53 | () => this.target.InitRequired()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Services.Test/DataStructures/ListExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.DataStructures; 5 | using Xunit; 6 | 7 | namespace Services.Test.DataStructures 8 | { 9 | public class ListExtensionsTest 10 | { 11 | [Fact] 12 | void ItShufflesAList() 13 | { 14 | // Arrange 15 | var unshuffled = new List() { 1, 2, 3, 4, 5 }; 16 | 17 | // Act 18 | var shuffled = new List(unshuffled); 19 | shuffled.Shuffle(); 20 | 21 | // Assert 22 | Assert.Equal(unshuffled.Count, shuffled.Count); 23 | Assert.True(shuffled.Count > 0); 24 | var matches = 0; 25 | var cursor = unshuffled.Count; 26 | while (cursor-- > 1) 27 | { 28 | if (unshuffled[cursor] == shuffled[cursor]) 29 | { 30 | matches++; 31 | } 32 | } 33 | 34 | Assert.True(matches < unshuffled.Count); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Services.Test/IntegrationTests/README.md: -------------------------------------------------------------------------------- 1 | Integration Tests 2 | ================= 3 | 4 | ## Guidelines 5 | 6 | * Store here functional and integration tests, e.g. tests which require network 7 | access, storage read/write operations, etc. 8 | * Functional and Integration tests typically requires confuiguration settings, 9 | which should be provided similarly to the entry point application. 10 | 11 | ## Conventions 12 | 13 | * For each scenario create a test class with "Test" suffix. 14 | * Flag all the tests with `[Fact, Trait(Constants.Type, Constants.IntegrationTest)]` 15 | -------------------------------------------------------------------------------- /Services.Test/IotHubMetricsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.AzureManagementAdapter; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub; 5 | using Moq; 6 | using Services.Test.helpers; 7 | using Xunit; 8 | 9 | namespace Services.Test 10 | { 11 | public class IotHubMetricsTest 12 | { 13 | private readonly Mock azureManagementAdapterClient; 14 | private readonly IotHubMetrics target; 15 | 16 | public IotHubMetricsTest() 17 | { 18 | this.azureManagementAdapterClient = new Mock(); 19 | this.target = new IotHubMetrics(this.azureManagementAdapterClient.Object); 20 | } 21 | 22 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 23 | public void ItInvokesAzureManagementAdapterOnceWhenQueryIothubMetrics() 24 | { 25 | // Act 26 | this.target.GetIothubMetricsAsync(It.IsAny()) 27 | .Wait(Constants.TEST_TIMEOUT); 28 | 29 | // Assert 30 | this.azureManagementAdapterClient.Verify( 31 | x => x.PostAsync(It.IsAny()), 32 | Times.Once); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Services.Test/README.md: -------------------------------------------------------------------------------- 1 | Unit Tests and Integration Tests 2 | ================================ 3 | 4 | ## Guidelines 5 | 6 | 7 | 8 | ## Conventions 9 | 10 | * For each class create a test class with "Test" suffix. 11 | * Flag all the tests with a type, e.g. `[Fact, Trait(Constants.Type, Constants.UnitTest)]` 12 | * Store Integration Tests under `IntegrationTests/` and use 13 | the `[Fact, Trait(Constants.Type, Constants.IntegrationTest)]` attribute 14 | -------------------------------------------------------------------------------- /Services.Test/Services.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.1 4 | latest 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Services.Test/Storage/ConfigTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage; 4 | using Services.Test.helpers; 5 | using Xunit; 6 | 7 | namespace Services.Test.Storage 8 | { 9 | public class ConfigTest 10 | { 11 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 12 | public void ItDoesntHaveADefaultStorageType() 13 | { 14 | // Act 15 | var target = new Config(); 16 | 17 | // Assert 18 | Assert.Equal(Type.Unknown, target.StorageType); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Services.Test/helpers/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Services.Test.helpers 4 | { 5 | public static class Constants 6 | { 7 | // Use this to kill unit tests not supposed to run async code 8 | public const int TEST_TIMEOUT = 5000; 9 | 10 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 11 | public const string TYPE = "Type"; 12 | public const string UNIT_TEST = "UnitTest"; 13 | public const string INTEGRATION_TEST = "IntegrationTest"; 14 | 15 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 16 | public const string SPEED = "Speed"; 17 | public const string SLOW_TEST = "SlowTest"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Services.Test/helpers/SomeException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Services.Test.helpers 6 | { 7 | public class SomeException : Exception 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Services.Test/helpers/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Xunit.Sdk; 5 | 6 | namespace Services.Test.helpers 7 | { 8 | /** 9 | * Use this class when testing asynchronous code, to avoid tests 10 | * running forever, e.g. in case threads don't end as expected. 11 | * 12 | * Example: 13 | * 14 | * this.target.SomeMethodAsync().CompleteOrTimeout(); 15 | * 16 | * var result = this.target.SomeMethodAsync().CompleteOrTimeout().Result; 17 | */ 18 | public static class TaskExtensions 19 | { 20 | // Wait for the task to complete or timeout 21 | public static Task CompleteOrTimeout(this Task t) 22 | { 23 | var complete = t.Wait(Constants.TEST_TIMEOUT); 24 | if (!complete) 25 | { 26 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 27 | } 28 | 29 | return t; 30 | } 31 | 32 | // Wait for the task to complete or timeout 33 | public static Task CompleteOrTimeout(this Task t) 34 | { 35 | var complete = t.Wait(Constants.TEST_TIMEOUT); 36 | if (!complete) 37 | { 38 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 39 | } 40 | 41 | return t; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Services/AzureManagementAdapter/AadTokenModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.AzureManagementAdapter 7 | { 8 | public class AadTokenModel 9 | { 10 | [JsonProperty("token_type", NullValueHandling = NullValueHandling.Ignore)] 11 | public string TokenType { get; set; } 12 | 13 | [JsonProperty("expires_on", NullValueHandling = NullValueHandling.Ignore)] 14 | public string ExpiresOn { get; set; } 15 | 16 | [JsonProperty("resource", NullValueHandling = NullValueHandling.Ignore)] 17 | public string Resource { get; set; } 18 | 19 | [JsonProperty("access_token", NullValueHandling = NullValueHandling.Ignore)] 20 | public string AccessToken { get; set; } 21 | 22 | [JsonProperty("error", NullValueHandling = NullValueHandling.Ignore)] 23 | public string Error { get; set; } 24 | 25 | [JsonProperty("error_description", NullValueHandling = NullValueHandling.Ignore)] 26 | public string ErrorDescription { get; set; } 27 | 28 | [JsonProperty("error_codes", NullValueHandling = NullValueHandling.Ignore)] 29 | public List ErrorCodes { get; set; } 30 | 31 | [JsonProperty("trace_id", NullValueHandling = NullValueHandling.Ignore)] 32 | public string TraceId { get; set; } 33 | 34 | [JsonProperty("correlation_id", NullValueHandling = NullValueHandling.Ignore)] 35 | public string CorrelationId { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Services/AzureManagementAdapter/AutoScaleSettingsModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.AzureManagementAdapter 7 | { 8 | public class AutoScaleSettingsCreateOrUpdateRequestModel 9 | { 10 | [JsonProperty("location")] 11 | public string Location { get; set; } 12 | 13 | [JsonProperty(PropertyName = "properties")] 14 | public Properties Properties { get; set; } 15 | } 16 | 17 | public class Properties 18 | { 19 | [JsonProperty("enabled")] 20 | public bool Enabled { get; set; } 21 | 22 | [JsonProperty("targetResourceUri")] 23 | public string TargetResourceUri { get; set; } 24 | 25 | [JsonProperty("profiles")] 26 | public List Profiles { get; set; } 27 | } 28 | 29 | public class Profile 30 | { 31 | [JsonProperty("name")] 32 | public string Name { get; set; } 33 | 34 | [JsonProperty("capacity")] 35 | public Capacity Capacity { get; set; } 36 | 37 | [JsonProperty("rules")] 38 | public List Rules { get; set; } 39 | } 40 | 41 | public class Capacity 42 | { 43 | [JsonProperty("minimum")] 44 | public string Minimum { get; set; } 45 | 46 | [JsonProperty("maximum")] 47 | public string Maximum { get; set; } 48 | 49 | [JsonProperty("default")] 50 | public string Default { get; set; } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Services/Concurrency/ETags.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /* CODE TEMPORARILY COMMENTED OUT 4 | 5 | using System; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency 8 | { 9 | public static class ETags 10 | { 11 | // A simple string generator, until we have a real storage, used for 12 | // optimistic concurrency on data stored on files 13 | public static string NewETag() 14 | { 15 | var v1 = Guid.NewGuid().ToString("N"); 16 | var v2 = DateTime.UtcNow.Ticks % 1000000; 17 | return v1.Substring(0, 8) + v2; 18 | } 19 | } 20 | } 21 | */ -------------------------------------------------------------------------------- /Services/Concurrency/LoopSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency 6 | { 7 | public class ConnectionLoopSettings 8 | { 9 | private readonly IRateLimitingConfig ratingConfig; 10 | 11 | public double SchedulableFetches { get; set; } 12 | public double SchedulableRegistrations { get; set; } 13 | 14 | public ConnectionLoopSettings(IRateLimitingConfig ratingConfig) 15 | { 16 | this.ratingConfig = ratingConfig; 17 | this.NewLoop(); 18 | } 19 | 20 | public void NewLoop() 21 | { 22 | // Prioritize connections and registrations, so that devices connect as soon as possible 23 | this.SchedulableFetches = Math.Max(1, this.ratingConfig.RegistryOperationsPerMinute / 25); 24 | this.SchedulableRegistrations = Math.Max(1, this.ratingConfig.RegistryOperationsPerMinute / 10); 25 | } 26 | } 27 | 28 | public class PropertiesLoopSettings 29 | { 30 | private const int SHARED_TWIN_WRITES_ALLOCATION = 2; 31 | 32 | private readonly IRateLimitingConfig ratingConfig; 33 | 34 | public double SchedulableTaggings { get; set; } 35 | 36 | public PropertiesLoopSettings(IRateLimitingConfig ratingConfig) 37 | { 38 | this.ratingConfig = ratingConfig; 39 | this.NewLoop(); 40 | } 41 | 42 | public void NewLoop() 43 | { 44 | // In order for other threads to be able to schedule twin operations, 45 | // divide by a constant value to prevent the tagging thread from having 46 | // first priority over twin writes all of the time. 47 | this.SchedulableTaggings = Math.Max(1, this.ratingConfig.TwinWritesPerSecond / SHARED_TWIN_WRITES_ALLOCATION); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Services/Concurrency/RateLimitingConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency 4 | { 5 | public interface IRateLimitingConfig 6 | { 7 | int RegistryOperationsPerMinute { get; } 8 | int TwinReadsPerSecond { get; } 9 | int TwinWritesPerSecond { get; } 10 | int ConnectionsPerSecond { get; } 11 | int DeviceMessagesPerSecond { get; } 12 | } 13 | 14 | public class RateLimitingConfig : IRateLimitingConfig 15 | { 16 | public int RegistryOperationsPerMinute { get; set; } 17 | public int TwinReadsPerSecond { get; set; } 18 | public int TwinWritesPerSecond { get; set; } 19 | public int ConnectionsPerSecond { get; set; } 20 | public int DeviceMessagesPerSecond { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Services/Concurrency/ThreadWrapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency 6 | { 7 | public interface IThreadWrapper 8 | { 9 | void Sleep(int msecs); 10 | } 11 | 12 | // Simple Thread wrapper to remove static methods and simplify testing 13 | public class ThreadWrapper: IThreadWrapper 14 | { 15 | public void Sleep(int msecs) 16 | { 17 | Thread.Sleep(msecs); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Services/DataStructures/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.DataStructures 8 | { 9 | public static class ListExtensions 10 | { 11 | // Note: fields marked with [ThreadStatic] must be static and not initialized statically. 12 | [ThreadStatic] 13 | private static Random rnd; 14 | 15 | // Thread safe random generator. 16 | private static Random Rnd => rnd ?? (rnd = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); 17 | 18 | // Fisher-Yates shuffle 19 | public static void Shuffle(this IList list) 20 | { 21 | if (list.Count < 2) return; 22 | 23 | var cursor = list.Count; 24 | 25 | while (cursor-- > 1) 26 | { 27 | var randomPosition = Rnd.Next(cursor + 1); 28 | var value = list[randomPosition]; 29 | list[randomPosition] = list[cursor]; 30 | list[cursor] = value; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Services/Diagnostics/IActorsLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics 4 | { 5 | public interface IActorsLogger 6 | { 7 | void Init(string deviceId, string actorName); 8 | void ActorStarted(); 9 | void ActorStopped(); 10 | void CredentialsSetupScheduled(long time); 11 | void FetchScheduled(long time); 12 | void PreparingDeviceCredentials(); 13 | void FetchingDevice(); 14 | void DeviceCredentialsReady(); 15 | void DeviceFetched(); 16 | void DeviceNotFound(); 17 | void DeviceFetchFailed(); 18 | void RegistrationScheduled(long time); 19 | void RegisteringDevice(); 20 | void DeviceRegistered(); 21 | void DeviceRegistrationFailed(); 22 | void DeregistrationScheduled(long time); 23 | void DeregisteringDevice(); 24 | void DeviceDeregistered(); 25 | void DeviceDeregistrationFailed(); 26 | void DeviceQuotaExceeded(); 27 | 28 | void DeviceTaggingScheduled(long time); 29 | void TaggingDevice(); 30 | void DeviceTagged(); 31 | void DeviceTaggingFailed(); 32 | 33 | void DeviceConnectionScheduled(long time); 34 | void ConnectingDevice(); 35 | void DeviceConnected(); 36 | 37 | void DeviceDisconnectionFailed(); 38 | void DeviceDisconnectionScheduled(long time); 39 | void DisconnectingDevice(); 40 | void DeviceDisconnected(); 41 | 42 | void DeviceConnectionAuthFailed(); 43 | void DeviceConnectionFailed(); 44 | 45 | void TelemetryScheduled(long time); 46 | void SendingTelemetry(); 47 | void TelemetryDelivered(); 48 | void TelemetryFailed(); 49 | void DailyTelemetryQuotaExceeded(); 50 | void TelemetryPaused(long time); 51 | 52 | void DevicePropertiesUpdateScheduled(long time, bool isRetry); 53 | void UpdatingDeviceProperties(); 54 | void DevicePropertiesUpdated(); 55 | void DevicePropertiesUpdateFailed(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Services/Diagnostics/LoggingConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics 6 | { 7 | public enum LogLevel 8 | { 9 | Debug = 10, 10 | Info = 20, 11 | Warn = 30, 12 | Error = 40, 13 | Always = 1000 14 | } 15 | 16 | public interface ILoggingConfig 17 | { 18 | LogLevel LogLevel { get; } 19 | bool LogProcessId { get; } 20 | bool ExtraDiagnostics { get; } 21 | string ExtraDiagnosticsPath { get; } 22 | string DateFormat { get; } 23 | HashSet BlackList { get; } 24 | HashSet WhiteList { get; } 25 | } 26 | 27 | // Note: singleton class 28 | public class LoggingConfig : ILoggingConfig 29 | { 30 | public const LogLevel DEFAULT_LOGLEVEL = LogLevel.Warn; 31 | public const string DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.fff"; 32 | 33 | public LogLevel LogLevel { get; set; } 34 | public bool LogProcessId { get; set; } 35 | public bool ExtraDiagnostics { get; set; } 36 | public string ExtraDiagnosticsPath { get; set; } 37 | public string DateFormat { get; set; } 38 | public HashSet BlackList { get; set; } 39 | public HashSet WhiteList { get; set; } 40 | 41 | public LoggingConfig() 42 | { 43 | this.LogLevel = DEFAULT_LOGLEVEL; 44 | this.LogProcessId = true; 45 | this.ExtraDiagnostics = false; 46 | this.DateFormat = DEFAULT_DATE_FORMAT; 47 | this.BlackList = new HashSet(); 48 | this.WhiteList = new HashSet(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Services/Exceptions/BrokenDeviceClientException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a device client is broken and should be recreated 9 | /// 10 | public class BrokenDeviceClientException : CustomException 11 | { 12 | public BrokenDeviceClientException(string message) : base(message) 13 | { 14 | } 15 | 16 | public BrokenDeviceClientException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Services/Exceptions/ConflictingResourceException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a client attempts to create a resource 9 | /// which would conflict with an existing one, for instance using the same 10 | /// identifier. The client should change the identifier or assume the 11 | /// resource has already been created. 12 | /// 13 | public class ConflictingResourceException : CustomException 14 | { 15 | public ConflictingResourceException() : base() 16 | { 17 | } 18 | 19 | public ConflictingResourceException(string message) : base(message) 20 | { 21 | } 22 | 23 | public ConflictingResourceException(string message, Exception innerException) 24 | : base(message, innerException) 25 | { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Services/Exceptions/CustomException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// The purpose of this exception is to be a base exception for all other 9 | /// custom exception, so that try-catch can catch exceptions generated by 10 | /// our code and avoid wrapping them with another custom exception. 11 | /// 12 | /// For instance, instead of writing: 13 | /// 14 | /// try { ...code... } 15 | /// › catch (InvalidInputException) { throw; } 16 | /// › catch (ResourceNotFoundException) { throw; } 17 | /// › catch (ConflictingResourceException) { throw; } 18 | /// › catch (ExternalDependencyException) { throw; } 19 | /// catch (Exception) { ... do something ... } 20 | /// 21 | /// we can write: 22 | /// 23 | /// try { ...code... } 24 | /// › catch (CustomException) { throw; } ‹ 25 | /// catch (Exception) { ... do something ... } 26 | /// 27 | /// reducing the amount of redundant code. 28 | /// 29 | public abstract class CustomException : Exception 30 | { 31 | protected CustomException() : base() 32 | { 33 | } 34 | 35 | protected CustomException(string message) : base(message) 36 | { 37 | } 38 | 39 | protected CustomException(string message, Exception innerException) 40 | : base(message, innerException) 41 | { 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Services/Exceptions/DailyTelemetryQuotaExceededException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | // This exception is thrown when a device client reaches the daily quota 8 | public class DailyTelemetryQuotaExceededException : CustomException 9 | { 10 | public DailyTelemetryQuotaExceededException(string message) : base(message) 11 | { 12 | } 13 | 14 | public DailyTelemetryQuotaExceededException(string message, Exception innerException) : base(message, innerException) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Services/Exceptions/DeviceAuthFailedException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a client attempts to connect and IoT Hub refuses 9 | /// the connection due to incorrect credentials. 10 | /// 11 | public class DeviceAuthFailedException : CustomException 12 | { 13 | public DeviceAuthFailedException() : base() 14 | { 15 | } 16 | 17 | public DeviceAuthFailedException(Exception innerException) 18 | : base(innerException.Message, innerException) 19 | { 20 | } 21 | 22 | public DeviceAuthFailedException(string message) : base(message) 23 | { 24 | } 25 | 26 | public DeviceAuthFailedException(string message, Exception innerException) 27 | : base(message, innerException) 28 | { 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Services/Exceptions/ExternalDependencyException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when the service failed to communicate with 9 | /// an external dependency, either because the dependency is unavailable 10 | /// or because it is failing with a 'retriable' error (e.g. timeout). 11 | /// 12 | /// When used for HTTP requests, a request error is retriable if: 13 | /// * the request timed out, or 14 | /// * the status code is one of 404, 408, 429 15 | /// 16 | public class ExternalDependencyException : CustomException 17 | { 18 | public ExternalDependencyException() : base() 19 | { 20 | } 21 | 22 | public ExternalDependencyException(string message) : base(message) 23 | { 24 | } 25 | 26 | public ExternalDependencyException(Exception innerException) 27 | : base("An unexpected error happened while using an external dependency.", innerException) 28 | { 29 | } 30 | 31 | public ExternalDependencyException(string message, Exception innerException) 32 | : base(message, innerException) 33 | { 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Services/Exceptions/InvalidConfigurationException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when the service is configured incorrectly. 9 | /// In order to recover, the service owner should fix the configuration 10 | /// and re-deploy the service. 11 | /// 12 | public class InvalidConfigurationException : CustomException 13 | { 14 | public InvalidConfigurationException() : base() 15 | { 16 | } 17 | 18 | public InvalidConfigurationException(string message) : base(message) 19 | { 20 | } 21 | 22 | public InvalidConfigurationException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Services/Exceptions/InvalidInputException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a client sends a request badly formatted 9 | /// or containing invalid values. The client should fix the request before 10 | /// retrying. 11 | /// 12 | public class InvalidInputException : CustomException 13 | { 14 | public InvalidInputException() : base() 15 | { 16 | } 17 | 18 | public InvalidInputException(string message) : base(message) 19 | { 20 | } 21 | 22 | public InvalidInputException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Services/Exceptions/InvalidIotHubConnectionStringFormatException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when the IoTHub connection string provided 9 | /// is not properly formatted. The correct format is: 10 | /// HostName=[hubname];SharedAccessKeyName=[iothubowner or service];SharedAccessKey=[null or valid key] 11 | /// 12 | public class InvalidIotHubConnectionStringFormatException : CustomException 13 | { 14 | public InvalidIotHubConnectionStringFormatException() : base() 15 | { 16 | } 17 | 18 | public InvalidIotHubConnectionStringFormatException(string message) : base(message) 19 | { 20 | } 21 | 22 | public InvalidIotHubConnectionStringFormatException(string message, Exception innerException) : base(message, innerException) 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Services/Exceptions/IotHubConnectionException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a connection to the IoTHub with the 9 | /// provided connection string fails. 10 | /// 11 | public class IotHubConnectionException : CustomException 12 | { 13 | public IotHubConnectionException() : base() 14 | { 15 | } 16 | 17 | public IotHubConnectionException(string message) : base(message) 18 | { 19 | } 20 | 21 | public IotHubConnectionException(string message, Exception innerException) : base(message, innerException) 22 | { 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Services/Exceptions/PropertySendException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when unable to send device twin reported properties 9 | /// 10 | public class PropertySendException : CustomException 11 | { 12 | public PropertySendException(string message) : base(message) 13 | { 14 | } 15 | 16 | public PropertySendException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Services/Exceptions/ResourceIsLockedByAnotherOwnerException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | public class ResourceIsLockedByAnotherOwnerException : CustomException 8 | { 9 | public ResourceIsLockedByAnotherOwnerException() : base() 10 | { 11 | } 12 | 13 | public ResourceIsLockedByAnotherOwnerException(string message) : base(message) 14 | { 15 | } 16 | 17 | public ResourceIsLockedByAnotherOwnerException(string message, Exception innerException) 18 | : base(message, innerException) 19 | { 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Services/Exceptions/ResourceNotFoundException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a client is requesting a resource that 9 | /// doesn't exist yet. 10 | /// 11 | public class ResourceNotFoundException : CustomException 12 | { 13 | public ResourceNotFoundException() : base() 14 | { 15 | } 16 | 17 | public ResourceNotFoundException(string message) : base(message) 18 | { 19 | } 20 | 21 | public ResourceNotFoundException(string message, Exception innerException) 22 | : base(message, innerException) 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Services/Exceptions/ResourceOutOfDateException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a client attempts to update a resource 9 | /// providing the wrong ETag value. The client should retrieve the 10 | /// resource again, to have the new ETag, and retry. 11 | /// 12 | public class ResourceOutOfDateException : CustomException 13 | { 14 | public ResourceOutOfDateException() : base() 15 | { 16 | } 17 | 18 | public ResourceOutOfDateException(string message) : base(message) 19 | { 20 | } 21 | 22 | public ResourceOutOfDateException(string message, Exception innerException) 23 | : base(message, innerException) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Services/Exceptions/TelemetrySendException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a device failed sending messages to IoTHub. 9 | /// 10 | public class TelemetrySendException : CustomException 11 | { 12 | public TelemetrySendException(string message) : base(message) 13 | { 14 | } 15 | 16 | public TelemetrySendException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Services/Exceptions/TelemetrySendIOException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a device fails to send a messages due to a IO exception. 9 | /// 10 | // ReSharper disable once InconsistentNaming 11 | public class TelemetrySendIOException : CustomException 12 | { 13 | public TelemetrySendIOException(string message) : base(message) 14 | { 15 | } 16 | 17 | public TelemetrySendIOException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Services/Exceptions/TelemetrySendTimeoutException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a device fails to send a messages due to a timeout, typically due to throttling. 9 | /// 10 | public class TelemetrySendTimeoutException : CustomException 11 | { 12 | public TelemetrySendTimeoutException(string message) : base(message) 13 | { 14 | } 15 | 16 | public TelemetrySendTimeoutException(string message, Exception innerException) : base(message, innerException) 17 | { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Services/Exceptions/TotalDeviceCountQuotaExceededException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | // This exception is thrown when it's not possible to register a device due to quota limits 8 | public class TotalDeviceCountQuotaExceededException : CustomException 9 | { 10 | public TotalDeviceCountQuotaExceededException(string message) : base(message) 11 | { 12 | } 13 | 14 | public TotalDeviceCountQuotaExceededException(string message, Exception innerException) : base(message, innerException) 15 | { 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Services/Exceptions/UnknownMessageFormatException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions 6 | { 7 | /// 8 | /// This exception is thrown when a client is requesting a message format that 9 | /// doesn't exist yet. 10 | /// 11 | public class UnknownMessageFormatException : CustomException 12 | { 13 | public UnknownMessageFormatException() : base() 14 | { 15 | } 16 | 17 | public UnknownMessageFormatException(string message) : base(message) 18 | { 19 | } 20 | 21 | public UnknownMessageFormatException(string message, Exception innerException) 22 | : base(message, innerException) 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Services/FileWrapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.IO; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services 6 | { 7 | public interface IFileSystem 8 | { 9 | bool Exists(string path); 10 | string ReadAllText(string path); 11 | } 12 | 13 | public class FileWrapper : IFileSystem 14 | { 15 | public bool Exists(string path) 16 | { 17 | return File.Exists(path); 18 | } 19 | 20 | public string ReadAllText(string path) 21 | { 22 | return File.ReadAllText(path); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Services/Http/HttpRequestOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http 4 | { 5 | public class HttpRequestOptions 6 | { 7 | public bool EnsureSuccess { get; set; } = false; 8 | 9 | public bool AllowInsecureSSLServer { get; set; } = false; 10 | 11 | public int Timeout { get; set; } = 30000; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Services/IotHub/IothubMetrics.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.AzureManagementAdapter; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub 7 | { 8 | // retrieves Iot Hub metrics from Azure management API 9 | public interface IIothubMetrics 10 | { 11 | Task GetIothubMetricsAsync(MetricsRequestListModel requestList); 12 | } 13 | 14 | public class IotHubMetrics : IIothubMetrics 15 | { 16 | private readonly IAzureManagementAdapterClient azureManagementAdapter; 17 | 18 | public IotHubMetrics(IAzureManagementAdapterClient azureManagementAdapter) 19 | { 20 | this.azureManagementAdapter = azureManagementAdapter; 21 | } 22 | 23 | /// 24 | /// Query Azure management API for the iothub metrics. 25 | /// 26 | /// Responses from the Azure management API 27 | public async Task GetIothubMetricsAsync(MetricsRequestListModel requestList) 28 | { 29 | return await this.azureManagementAdapter.PostAsync(requestList); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Services/IotHub/ServiceClientWrapper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using Microsoft.Azure.Devices; 6 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.DataStructures; 7 | 8 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub 9 | { 10 | public interface IServiceClient 11 | { 12 | void Init(string connString); 13 | 14 | Task GetServiceStatisticsAsync(); 15 | 16 | void Dispose(); 17 | } 18 | 19 | public class ServiceClientWrapper : IServiceClient, IDisposable 20 | { 21 | private IInstance instance; 22 | private ServiceClient serviceClient; 23 | 24 | public ServiceClientWrapper(IInstance instance) 25 | { 26 | this.instance = instance; 27 | } 28 | 29 | public void Init(string connString) 30 | { 31 | this.instance.InitOnce(); 32 | this.serviceClient = ServiceClient.CreateFromConnectionString(connString); 33 | this.instance.InitComplete(); 34 | } 35 | 36 | public async Task GetServiceStatisticsAsync() 37 | { 38 | this.instance.InitRequired(); 39 | return await this.serviceClient.GetServiceStatisticsAsync(); 40 | } 41 | 42 | public void Dispose() 43 | { 44 | this.ReleaseResources(); 45 | } 46 | 47 | ~ServiceClientWrapper() 48 | { 49 | this.ReleaseResources(); 50 | } 51 | 52 | private void ReleaseResources() 53 | { 54 | this.serviceClient?.Dispose(); 55 | this.instance = null; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Services/Models/DataFile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Runtime.Serialization; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Converters; 7 | 8 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models 9 | { 10 | public class DataFile 11 | { 12 | [JsonIgnore] 13 | public string ETag { get; set; } 14 | public string Id { get; set; } 15 | public string Name { get; set; } 16 | public string Type { get; set; } 17 | public string Content { get; set; } 18 | public FilePath Path { get; set; } 19 | public DateTimeOffset Created { get; set; } 20 | public DateTimeOffset Modified { get; set; } 21 | 22 | public DataFile() 23 | { 24 | this.ETag = string.Empty; 25 | this.Id = string.Empty; 26 | this.Type = string.Empty; 27 | this.Content = string.Empty; 28 | this.Path = FilePath.Storage; 29 | this.Name = string.Empty; 30 | } 31 | 32 | [JsonConverter(typeof(StringEnumConverter))] 33 | public enum FilePath 34 | { 35 | [EnumMember(Value = "Undefined")] 36 | Undefined = 0, 37 | 38 | [EnumMember(Value = "Storage")] 39 | Storage = 10 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Services/Models/Device.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.Devices; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models 7 | { 8 | public class Device 9 | { 10 | public string ETag { get; set; } 11 | public string Id { get; set; } 12 | public int C2DMessageCount { get; set; } 13 | public DateTimeOffset LastActivity { get; set; } 14 | public bool Connected { get; set; } 15 | public bool Enabled { get; set; } 16 | public DateTimeOffset LastStatusUpdated { get; set; } 17 | public string IoTHubHostName { get; set; } 18 | public string AuthPrimaryKey { get; set; } 19 | 20 | public Device( 21 | string eTag, 22 | string id, 23 | int c2DMessageCount, 24 | DateTimeOffset lastActivity, 25 | bool connected, 26 | bool enabled, 27 | DateTimeOffset lastStatusUpdated, 28 | string primaryKey, 29 | string ioTHubHostName) 30 | { 31 | this.ETag = eTag; 32 | this.Id = id; 33 | this.C2DMessageCount = c2DMessageCount; 34 | this.LastActivity = lastActivity; 35 | this.Connected = connected; 36 | this.Enabled = enabled; 37 | this.LastStatusUpdated = lastStatusUpdated; 38 | this.IoTHubHostName = ioTHubHostName; 39 | this.AuthPrimaryKey = primaryKey; 40 | } 41 | 42 | public Device(Azure.Devices.Device azureDevice, string ioTHubHostName) : 43 | this( 44 | eTag: azureDevice.ETag, 45 | id: azureDevice.Id, 46 | c2DMessageCount: azureDevice.CloudToDeviceMessageCount, 47 | lastActivity: azureDevice.LastActivityTime, 48 | connected: azureDevice.ConnectionState.Equals(DeviceConnectionState.Connected), 49 | enabled: azureDevice.Status.Equals(DeviceStatus.Enabled), 50 | lastStatusUpdated: azureDevice.StatusUpdatedTime, 51 | ioTHubHostName: ioTHubHostName, 52 | primaryKey: azureDevice.Authentication.SymmetricKey.PrimaryKey) 53 | { 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Services/Models/DevicesPartition.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models 7 | { 8 | public class DevicesPartition 9 | { 10 | [JsonProperty(Order = 1)] 11 | public string Id { get; set; } 12 | 13 | [JsonProperty(Order = 10)] 14 | public string SimulationId { get; set; } 15 | 16 | [JsonProperty(Order = 20)] 17 | public int Size { get; set; } 18 | 19 | [JsonProperty(Order = 100)] 20 | public Dictionary> DeviceIdsByModel { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Services/Models/IoTHubProtocol.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models 4 | { 5 | public enum IoTHubProtocol 6 | { 7 | AMQP = 1, 8 | MQTT = 2, 9 | HTTP = 3 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Services/Models/Protobuf/proto/Truck.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Protobuf"; 4 | 5 | message Truck { 6 | double latitude=1; 7 | double longitude=2; 8 | double speed=3; 9 | string speed_unit=4; 10 | double temperature=5; 11 | string temperature_unit=6; 12 | } -------------------------------------------------------------------------------- /Services/Models/README.md: -------------------------------------------------------------------------------- 1 | Service layer models 2 | ==================== 3 | 4 | ## Guidelines 5 | 6 | * Do not reference classes from the "webservice" project. 7 | * Ensure datetime values are transfered using UTC timezone. 8 | * Use `new DateTimeOffset(azureDevice.LastActivityTime, TimeSpan.Zero)` 9 | to parse datetime values returned by Azure IoT SDK. 10 | 11 | ## Conventions 12 | 13 | * For DateTime fields use System.DateTimeOffset. 14 | -------------------------------------------------------------------------------- /Services/Models/SimulationPatch.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models 4 | { 5 | public class SimulationPatch 6 | { 7 | public string ETag { get; set; } 8 | public string Id { get; set; } 9 | public bool? Enabled { get; set; } 10 | public bool? DeleteDevicesOnce { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Services/Models/SimulationStatisticsModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models 4 | { 5 | public class SimulationStatisticsModel 6 | { 7 | public long ActiveDevices { get; set; } 8 | public long TotalMessagesSent { get; set; } 9 | public long FailedMessages { get; set; } 10 | public long FailedDeviceConnections { get; set; } 11 | public long FailedDevicePropertiesUpdates { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Services/Runtime/DeploymentConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime 4 | { 5 | public interface IDeploymentConfig 6 | { 7 | string AzureSubscriptionDomain { get; } 8 | string AzureSubscriptionId { get; } 9 | string AzureResourceGroup { get; } 10 | string AzureResourceGroupLocation { get; } 11 | string AzureIothubName { get; } 12 | string AzureVmssName { get; } 13 | string AadTenantId { get; } 14 | string AadAppId { get; } 15 | string AadAppSecret { get; } 16 | string AadTokenUrl { get; } 17 | } 18 | 19 | public class DeploymentConfig : IDeploymentConfig 20 | { 21 | public string AzureSubscriptionDomain { get; set; } 22 | public string AzureSubscriptionId { get; set; } 23 | public string AzureResourceGroup { get; set; } 24 | public string AzureResourceGroupLocation { get; set; } 25 | public string AzureIothubName { get; set; } 26 | public string AzureVmssName { get; set; } 27 | public string AadTenantId { get; set; } 28 | public string AadAppId { get; set; } 29 | public string AadAppSecret { get; set; } 30 | public string AadTokenUrl { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Services/Runtime/Factory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime 6 | { 7 | /// 8 | /// Provide factory pattern for dependencies that are instantiated 9 | /// multiple times during the application lifetime. 10 | /// How to use: 11 | /// 12 | /// class MyClass : IMyClass { 13 | /// public MyClass(IFactory factory) { 14 | /// this.factory = factory; 15 | /// } 16 | /// public SomeMethod() { 17 | /// var instance1 = this.factory.Resolve(); 18 | /// var instance2 = this.factory.Resolve(); 19 | /// var instance3 = this.factory.Resolve(); 20 | /// } 21 | /// } 22 | /// 23 | /// 24 | public interface IFactory 25 | { 26 | T Resolve(); 27 | } 28 | 29 | public class Factory : IFactory 30 | { 31 | private static Func resolver; 32 | 33 | public T Resolve() 34 | { 35 | return (T)resolver.Invoke(typeof(T)); 36 | } 37 | 38 | public static void RegisterResolver(Func func) 39 | { 40 | resolver = func; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Services/Statistics/SimulationStatisticsRecord.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Statistics 6 | { 7 | public class SimulationStatisticsRecord 8 | { 9 | public string SimulationId { get; set; } 10 | public string NodeId { get; set; } 11 | public SimulationStatisticsModel Statistics { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Services/Storage/Config.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage 4 | { 5 | public class Config 6 | { 7 | private const int DEFAULT_MAX_PENDING_STORAGE_OPERATIONS = 25; 8 | private const int DEFAULT_COSMOSDBSQL_THROUGHPUT = 400; 9 | 10 | public Type StorageType { get; set; } 11 | public int MaxPendingOperations { get; set; } 12 | 13 | // Cosmos DB SQL parameters 14 | public string CosmosDbSqlConnString { get; set; } 15 | public string CosmosDbSqlDatabase { get; set; } 16 | public string CosmosDbSqlCollection { get; set; } 17 | public int CosmosDbSqlThroughput { get; set; } 18 | 19 | // Azure Table Storage parameters 20 | public string TableStorageConnString { get; set; } 21 | public string TableStorageTableName { get; set; } 22 | 23 | public Config() 24 | { 25 | this.StorageType = Type.Unknown; 26 | this.MaxPendingOperations = DEFAULT_MAX_PENDING_STORAGE_OPERATIONS; 27 | this.CosmosDbSqlThroughput = DEFAULT_COSMOSDBSQL_THROUGHPUT; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Services/Storage/Engines.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage 7 | { 8 | public interface IEngines 9 | { 10 | IEngine Build(Config config); 11 | } 12 | 13 | public class Engines : IEngines 14 | { 15 | private readonly IFactory factory; 16 | 17 | public Engines(IFactory factory) 18 | { 19 | this.factory = factory; 20 | } 21 | 22 | public IEngine Build(Config config) 23 | { 24 | IEngine engine; 25 | 26 | switch (config.StorageType) 27 | { 28 | case Type.CosmosDbSql: 29 | engine = this.factory.Resolve(); 30 | engine.Init(config); 31 | return engine; 32 | 33 | case Type.TableStorage: 34 | engine = this.factory.Resolve(); 35 | engine.Init(config); 36 | return engine; 37 | } 38 | 39 | throw new ArgumentOutOfRangeException("Unknown storage engine: " + config.StorageType); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Services/Storage/IDataRecord.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage 4 | { 5 | public interface IDataRecord 6 | { 7 | // Record ID. The underlying implementation depends on the storage engine, 8 | // for instance table storage uses PK and RK 9 | string GetId(); 10 | 11 | // ETag string, the value is managed directly by the storage engine in use 12 | string GetETag(); 13 | 14 | // ETag string. The value is not a "Property" to avoid it being saved twice. 15 | IDataRecord SetETag(string eTag); 16 | 17 | // JSON serialized data from the business layer 18 | IDataRecord SetData(string data); 19 | 20 | // JSON serialized data from the business layer 21 | string GetData(); 22 | 23 | // Set the record expiration 24 | void ExpiresInSecs(long secs); 25 | 26 | // Return true if the record is expired 27 | bool IsExpired(); 28 | 29 | // Return true if the record is locked by some client 30 | bool IsLocked(); 31 | 32 | // Return true if the record is locke by another client 33 | bool IsLockedByOthers(string ownerId, string ownerType); 34 | 35 | // Lock the record 36 | void Lock(string ownerId, string ownerType, long durationSeconds); 37 | 38 | // Return true if the client can unlock the record 39 | bool CanUnlock(string ownerId, string ownerType); 40 | 41 | // Unlock the record 42 | void Unlock(string ownerId, string ownerType); 43 | 44 | // Change the record last modified time 45 | void Touch(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Services/Storage/IEngine.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage 7 | { 8 | public interface IEngine 9 | { 10 | IEngine Init(Config config); 11 | IDataRecord BuildRecord(string id); 12 | IDataRecord BuildRecord(string id, string data); 13 | Task GetAsync(string id); 14 | Task ExistsAsync(string id); 15 | Task> GetAllAsync(); 16 | Task CreateAsync(IDataRecord input); 17 | Task UpsertAsync(IDataRecord input); 18 | Task UpsertAsync(IDataRecord input, string eTag); 19 | Task DeleteAsync(string id); 20 | Task DeleteMultiAsync(List ids); 21 | Task TryToLockAsync(string id, string ownerId, string ownerType, int durationSeconds); 22 | Task TryToUnlockAsync(string id, string ownerId, string ownerType); 23 | 24 | // TODO: implement this API to for scenarios where ETag mismatch doesn't matter 25 | // and the client wants to do a delete+insert (overwrite) 26 | //Task DiscardAndUpsertAsync(IDataRecord input); 27 | } 28 | } -------------------------------------------------------------------------------- /Services/Storage/Type.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage 4 | { 5 | public enum Type 6 | { 7 | Unknown = 0, 8 | CosmosDbSql, 9 | TableStorage 10 | } 11 | } -------------------------------------------------------------------------------- /Services/StorageAdapter/ValueApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter 7 | { 8 | public class ValueApiModel 9 | { 10 | [JsonProperty("Key")] 11 | public string Key { get; set; } 12 | 13 | [JsonProperty("Data")] 14 | public string Data { get; set; } 15 | 16 | [JsonProperty("ETag")] 17 | public string ETag { get; set; } 18 | 19 | [JsonProperty("$metadata")] 20 | public Dictionary Metadata; 21 | 22 | public ValueApiModel() 23 | { 24 | this.Metadata = new Dictionary(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Services/StorageAdapter/ValueListApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter 7 | { 8 | public class ValueListApiModel 9 | { 10 | public List Items { get; set; } 11 | 12 | [JsonProperty("$metadata")] 13 | public Dictionary Metadata { get; set; } 14 | 15 | public ValueListApiModel() 16 | { 17 | this.Items = new List(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Services/data/devicemodels/README.md: -------------------------------------------------------------------------------- 1 | For more information about simulated device models 2 | [see the wiki](https://github.com/Azure/device-simulation-dotnet/wiki/Device-Models). 3 | -------------------------------------------------------------------------------- /Services/data/devicemodels/delivery-truck-01.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "delivery-truck-01", 4 | "Version": "0.0.1", 5 | "Name": "Delivery Truck", 6 | "Description": "Truck on a repeating route with GPS, altitude, speed, and cargo temperature sensors.", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "altitude": 83.0, 12 | "current_data_index": 0, 13 | "latitude": 47.50066160, 14 | "longitude": -122.1859029, 15 | "speed": 30.0, 16 | "speed_unit": "mph", 17 | "temperature": 38.0, 18 | "temperature_unit": "F" 19 | }, 20 | "Interval": "00:00:10", 21 | "Scripts": [ 22 | { 23 | "Type": "javascript", 24 | "Path": "delivery-truck-01-state.js" 25 | } 26 | ] 27 | }, 28 | "Properties": { 29 | "Type": "Truck", 30 | "Location": "Field", 31 | "Latitude": 47.50066160, 32 | "Longitude": -122.1859029 33 | }, 34 | "Telemetry": [ 35 | { 36 | "Interval": "00:00:10", 37 | "MessageTemplate": "{\"altitude\":${altitude},\"latitude\":${latitude},\"longitude\":${longitude},\"speed\": ${speed},\"speed_unit\":\"${speed_unit}\",\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}", 38 | "MessageSchema": { 39 | "Name": "truck-sensors;v1", 40 | "Format": "JSON", 41 | "Fields": { 42 | "altitude": "double", 43 | "latitude": "double", 44 | "longitude": "double", 45 | "speed": "double", 46 | "speed_unit": "text", 47 | "temperature": "double", 48 | "temperature_unit": "text" 49 | } 50 | } 51 | } 52 | ], 53 | "CloudToDeviceMethods": { 54 | "FirmwareUpdate": { 55 | "Type": "javascript", 56 | "Path": "FirmwareUpdate-method.js" 57 | }, 58 | "DecreaseCargoTemperature": { 59 | "Type": "javascript", 60 | "Path": "TempDecrease-method.js" 61 | }, 62 | "IncreaseCargoTemperature": { 63 | "Type": "javascript", 64 | "Path": "TempIncrease-method.js" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Services/data/devicemodels/elevator-01.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "elevator-01", 4 | "Version": "0.0.1", 5 | "Name": "Elevator", 6 | "Description": "Elevator with floor, vibration and temperature sensors.", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "floor": 1, 12 | "vibration": 10.0, 13 | "vibration_unit": "mm", 14 | "temperature": 75.0, 15 | "temperature_unit": "F" 16 | }, 17 | "Interval": "00:00:10", 18 | "Scripts": [ 19 | { 20 | "Type": "javascript", 21 | "Path": "elevator-01-state.js" 22 | } 23 | ] 24 | }, 25 | "Properties": { 26 | "Type": "Elevator", 27 | "Location": "Building 40", 28 | "Latitude": 47.636369, 29 | "Longitude": -122.133132 30 | }, 31 | "Telemetry": [ 32 | { 33 | "Interval": "00:00:10", 34 | "MessageTemplate": "{\"floor\":${floor},\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\",\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}", 35 | "MessageSchema": { 36 | "Name": "elevator-sensors;v1", 37 | "Format": "JSON", 38 | "Fields": { 39 | "floor": "integer", 40 | "vibration": "double", 41 | "vibration_unit": "text", 42 | "temperature": "double", 43 | "temperature_unit": "text" 44 | } 45 | } 46 | } 47 | ], 48 | "CloudToDeviceMethods": { 49 | "FirmwareUpdate": { 50 | "Type": "javascript", 51 | "Path": "FirmwareUpdate-method.js" 52 | }, 53 | "StopElevator": { 54 | "Type": "javascript", 55 | "Path": "StopElevator-method.js" 56 | }, 57 | "StartElevator": { 58 | "Type": "javascript", 59 | "Path": "StartElevator-method.js" 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/elevator-02.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "elevator-02", 4 | "Version": "0.0.1", 5 | "Name": "Faulty Elevator", 6 | "Description": "Elevator with floor, vibration and temperature sensors. Elevator blocked on 4th floor.", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "floor": 4, 12 | "vibration": 10.0, 13 | "vibration_unit": "mm", 14 | "temperature": 75.0, 15 | "temperature_unit": "F" 16 | }, 17 | "Interval": "00:00:10", 18 | "Scripts": [ 19 | { 20 | "Type": "javascript", 21 | "Path": "elevator-02-state.js" 22 | } 23 | ] 24 | }, 25 | "Properties": { 26 | "Type": "Elevator", 27 | "Location": "Building 17", 28 | "Latitude": 47.643786, 29 | "Longitude": -122.128047 30 | }, 31 | "Telemetry": [ 32 | { 33 | "Interval": "00:00:10", 34 | "MessageTemplate": "{\"floor\":${floor},\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\",\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}", 35 | "MessageSchema": { 36 | "Name": "elevator-sensors;v1", 37 | "Format": "JSON", 38 | "Fields": { 39 | "floor": "integer", 40 | "vibration": "double", 41 | "vibration_unit": "text", 42 | "temperature": "double", 43 | "temperature_unit": "text" 44 | } 45 | } 46 | } 47 | ], 48 | "CloudToDeviceMethods": { 49 | "FirmwareUpdate": { 50 | "Type": "javascript", 51 | "Path": "FirmwareUpdate-method.js" 52 | }, 53 | "StopElevator": { 54 | "Type": "javascript", 55 | "Path": "StopElevator-method.js" 56 | }, 57 | "StartElevator": { 58 | "Type": "javascript", 59 | "Path": "StartElevator-method.js" 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/engine-01.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "engine-01", 4 | "Version": "0.0.1", 5 | "Name": "Engine", 6 | "Description": "Engine with fuel level, coolant and vibration sensors", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "fuellevel": 70.0, 12 | "fuellevel_unit": "Gal", 13 | "coolant": 7500.0, 14 | "coolant_unit": "ohm", 15 | "vibration": 10.0, 16 | "vibration_unit": "mm" 17 | }, 18 | "Interval": "00:00:10", 19 | "Scripts": [ 20 | { 21 | "Type": "javascript", 22 | "Path": "engine-01-state.js" 23 | 24 | } 25 | ] 26 | }, 27 | "Properties": { 28 | "Type": "Engine", 29 | "Location": "Factory 1", 30 | "Latitude": 47.65358, 31 | "Longitude": -122.133545 32 | }, 33 | "Telemetry": [ 34 | { 35 | "Interval": "00:00:10", 36 | "MessageTemplate": "{\"fuellevel\":${fuellevel},\"fuellevel_unit\":\"${fuellevel_unit}\",\"coolant\":${coolant},\"coolant_unit\":\"${coolant_unit}\",\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}", 37 | "MessageSchema": { 38 | "Name": "engine-sensors;v1", 39 | "Format": "JSON", 40 | "Fields": { 41 | "fuellevel": "double", 42 | "fuellevel_unit": "text", 43 | "coolant": "double", 44 | "coolant_unit": "text", 45 | "vibration": "double", 46 | "vibration_unit": "text" 47 | } 48 | } 49 | } 50 | ], 51 | "CloudToDeviceMethods": { 52 | "FirmwareUpdate": { 53 | "Type": "javascript", 54 | "Path": "FirmwareUpdate-method.js" 55 | }, 56 | "EmptyTank": { 57 | "Type": "javascript", 58 | "Path": "EmptyTank-method.js" 59 | }, 60 | "FillTank": { 61 | "Type": "javascript", 62 | "Path": "FillTank-method.js" 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/engine-02.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "engine-02", 4 | "Version": "0.0.1", 5 | "Name": "Faulty Engine", 6 | "Description": "Engine with fuel level, coolant and vibration sensors", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "fuellevel": 0.0, 12 | "fuellevel_unit": "Gal", 13 | "coolant": 7500.0, 14 | "coolant_unit": "ohm", 15 | "vibration": 0.0, 16 | "vibration_unit": "mm" 17 | }, 18 | "Interval": "00:00:10", 19 | "Scripts": [ 20 | { 21 | "Type": "javascript", 22 | "Path": "engine-02-state.js" 23 | } 24 | ] 25 | }, 26 | "Properties": { 27 | "Type": "Engine", 28 | "Location": "Factory 1", 29 | "Latitude": 47.583589, 30 | "Longitude": -122.13067 31 | }, 32 | "Telemetry": [ 33 | { 34 | "Interval": "00:00:10", 35 | "MessageTemplate": "{\"fuellevel\":${fuellevel},\"fuellevel_unit\":\"${fuellevel_unit}\",\"coolant\":${coolant},\"coolant_unit\":\"${coolant_unit}\",\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}", 36 | "MessageSchema": { 37 | "Name": "engine-sensors;v1", 38 | "Format": "JSON", 39 | "Fields": { 40 | "fuellevel": "double", 41 | "fuellevel_unit": "text", 42 | "coolant": "double", 43 | "coolant_unit": "text", 44 | "vibration": "double", 45 | "vibration_unit": "text" 46 | } 47 | } 48 | } 49 | ], 50 | "CloudToDeviceMethods": { 51 | "FirmwareUpdate": { 52 | "Type": "javascript", 53 | "Path": "FirmwareUpdate-method.js" 54 | }, 55 | "EmptyTank": { 56 | "Type": "javascript", 57 | "Path": "EmptyTank-method.js" 58 | }, 59 | "FillTank": { 60 | "Type": "javascript", 61 | "Path": "FillTank-method.js" 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/EmergencyValveRelease-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*global updateProperty*/ 6 | /*global sleep*/ 7 | /*jslint node: true*/ 8 | 9 | "use strict"; 10 | 11 | /** 12 | * Entry point function called by the simulation engine. 13 | * 14 | * @param context The context contains current time, device model and id 15 | * @param previousState The device state since the last iteration 16 | * @param previousProperties The device properties since the last iteration 17 | */ 18 | /*jslint unparam: true*/ 19 | function main(context, previousState, previousProperties) { 20 | 21 | log("Starting 'EmergencyValveRelease' JavaScript method simulation."); 22 | 23 | var state = { 24 | simulation_state: "normal_pressure", 25 | pressure: 150 26 | }; 27 | updateState(state); 28 | 29 | log("'Emergency Valve Release' JavaScript method simulation completed."); 30 | } 31 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/IncreasePressure-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*global sleep*/ 6 | /*jslint node: true*/ 7 | 8 | "use strict"; 9 | 10 | /** 11 | * Entry point function called by the simulation engine. 12 | * 13 | * @param context The context contains current time, device model and id, not used 14 | * @param previousState The device state since the last iteration, not used 15 | * @param previousProperties The device properties since the last iteration 16 | */ 17 | /*jslint unparam: true*/ 18 | function main(context, previousState, previousProperties) { 19 | 20 | log("Executing 'IncreasePressure' JavaScript method simulation (5 seconds)."); 21 | 22 | // Pause the simulation and change the simulation mode so that the 23 | // pressure will fluctuate at ~250 when it resumes 24 | var state = { 25 | simulation_state: "high_pressure", 26 | CalculateRandomizedTelemetry: false 27 | }; 28 | updateState(state); 29 | 30 | // Increase 31 | state.pressure = 170; 32 | updateState(state); 33 | log("Pressure increased to " + state.pressure); 34 | sleep(1000); 35 | 36 | // Increase 37 | state.pressure = 190; 38 | updateState(state); 39 | log("Pressure increased to " + state.pressure); 40 | sleep(1000); 41 | 42 | // Increase 43 | state.pressure = 210; 44 | updateState(state); 45 | log("Pressure increased to " + state.pressure); 46 | sleep(1000); 47 | 48 | // Increase 49 | state.pressure = 230; 50 | updateState(state); 51 | log("Pressure increased to " + state.pressure); 52 | sleep(1000); 53 | 54 | // Increase 55 | state.pressure = 250; 56 | updateState(state); 57 | log("Pressure increased to " + state.pressure); 58 | sleep(1000); 59 | 60 | // Resume the simulation 61 | state.CalculateRandomizedTelemetry = true; 62 | updateState(state); 63 | 64 | log("'IncreasePressure' JavaScript method simulation completed."); 65 | } 66 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/README.md: -------------------------------------------------------------------------------- 1 | For more information about simulated device models 2 | [see the wiki](https://github.com/Azure/device-simulation-dotnet/wiki/Device-Models). 3 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/Reboot-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*global sleep*/ 6 | /*jslint node: true*/ 7 | 8 | "use strict"; 9 | 10 | // Default state 11 | var state = { 12 | // reboot just changes whether the device is on or offline 13 | online: true 14 | }; 15 | 16 | /** 17 | * Entry point function called by the method. 18 | * 19 | * @param context The context contains current time, device model and id 20 | * @param previousState The device state since the last iteration 21 | * @param previousProperties The device properties since the last iteration 22 | */ 23 | /*jslint unparam: true*/ 24 | function main(context, previousState, previousProperties) { 25 | 26 | // Reboot - devices goes offline and comes online after 20 seconds 27 | log("Executing 'Reboot' JavaScript method simulation."); 28 | 29 | state.DeviceMethodStatus = "Rebooting device..."; 30 | state.CalculateRandomizedTelemetry = false; 31 | state.online = false; 32 | // update the state to offline 33 | updateState(state); 34 | 35 | // Sleep for 15 seconds 36 | sleep(15000); 37 | 38 | state.DeviceMethodStatus = "Successfully rebooted device."; 39 | updateState(state); 40 | 41 | // Sleep for 5 seconds 42 | sleep(5000); 43 | state.CalculateRandomizedTelemetry = true; 44 | // update the state back to online 45 | state.online = true; 46 | state.DeviceMethodStatus = ""; 47 | updateState(state); 48 | 49 | log("'Reboot' JavaScript method simulation completed."); 50 | } 51 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/SetFuelLevel-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*jslint node: true*/ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Entry point function called by the simulation engine. 11 | * 12 | * @param context The context contains current time, device model and id 13 | * @param previousState The device state since the last iteration 14 | * @param previousProperties The device properties since the last iteration 15 | */ 16 | /*jslint unparam: true*/ 17 | function main(context, previousState, previousProperties) { 18 | 19 | // General info indicating that this method is being called 20 | log("Executing 'SetFuelLevel' JavaScript method simulation."); 21 | 22 | // Input validation. Make sure that thew new 23 | // fuel level is within the allowable range 24 | var newFuelLevel = context.FuelLevel; 25 | if (isNaN(newFuelLevel) || !newFuelLevel || newFuelLevel < 0 || newFuelLevel > 70) { 26 | throw new Error("Invalid fuel level specified. Fuel level must be betwee 0 and 70."); 27 | } 28 | 29 | log("Setting fuel level to: " + newFuelLevel); 30 | 31 | var state = { 32 | fuellevel: newFuelLevel 33 | }; 34 | updateState(state); 35 | 36 | log("'SetFuelLevel' JavaScript method simulation completed."); 37 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/SetTemperature-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*jslint node: true*/ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Entry point function called by the simulation engine. 11 | * 12 | * @param context The context contains current time, device model and id 13 | * @param previousState The device state since the last iteration 14 | * @param previousProperties The device properties since the last iteration 15 | */ 16 | /*jslint unparam: true*/ 17 | function main(context, previousState, previousProperties) { 18 | 19 | // General info indicating that this method is being called 20 | log("Executing 'SetTemperature' JavaScript method simulation."); 21 | 22 | // Input validation. Make sure that thew new 23 | // temperature is within the allowable range 24 | var newTemperature = context.Temperature; 25 | if (isNaN(newTemperature) || !newTemperature || newTemperature < 0 || newTemperature > 100) { 26 | throw new Error("Invalid temperature specified. Temperature must be betwee 0 and 100."); 27 | } 28 | 29 | log("Setting temperature to: " + newTemperature); 30 | 31 | var state = { 32 | temperature: newTemperature 33 | }; 34 | updateState(state); 35 | 36 | log("'SetTemperature' JavaScript method simulation completed."); 37 | } 38 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/StartElevator-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*jslint node: true*/ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Entry point function called by the method. 11 | * 12 | * @param context The context contains current time, device model and id 13 | * @param previousState The device state since the last iteration 14 | * @param previousProperties The device properties since the last iteration 15 | */ 16 | /*jslint unparam: true*/ 17 | function main(context, previousState, previousProperties) { 18 | 19 | log("Executing 'StartElevator' JavaScript method simulation."); 20 | 21 | var state = { 22 | moving: true 23 | }; 24 | log("Starting elevator."); 25 | updateState(state); 26 | 27 | log("'StartElevator' JavaScript method simulation completed."); 28 | } 29 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/StartMovingDevice-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*jslint node: true*/ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Entry point function called by the method. 11 | * 12 | * @param context The context contains current time, device model and id 13 | * @param previousState The device state since the last iteration 14 | * @param previousProperties The device properties since the last iteration 15 | */ 16 | /*jslint unparam: true*/ 17 | function main(context, previousState, previousProperties) { 18 | 19 | log("Executing 'StartMovingDevice' JavaScript method simulation."); 20 | 21 | var state = { 22 | moving: true 23 | }; 24 | log("Starting device movement."); 25 | updateState(state); 26 | 27 | log("'StartMovingDevice' JavaScript method simulation completed."); 28 | } 29 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/StopElevator-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*jslint node: true*/ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Entry point function called by the method. 11 | * 12 | * @param context The context contains current time, device model and id 13 | * @param previousState The device state since the last iteration 14 | * @param previousProperties The device properties since the last iteration 15 | */ 16 | /*jslint unparam: true*/ 17 | function main(context, previousState, previousProperties) { 18 | 19 | log("Executing 'StopElevator' JavaScript method simulation."); 20 | 21 | var state = { 22 | moving: false 23 | }; 24 | log("Stopping elevator."); 25 | updateState(state); 26 | 27 | log("'StopElevator' JavaScript method simulation completed."); 28 | } 29 | -------------------------------------------------------------------------------- /Services/data/devicemodels/scripts/StopMovingDevice-method.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | /*global log*/ 4 | /*global updateState*/ 5 | /*jslint node: true*/ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Entry point function called by the method. 11 | * 12 | * @param context The context contains current time, device model and id 13 | * @param previousState The device state since the last iteration 14 | * @param previousProperties The device properties since the last iteration 15 | */ 16 | /*jslint unparam: true*/ 17 | function main(context, previousState, previousProperties) { 18 | 19 | log("Executing 'StopMovingDevice' JavaScript method simulation."); 20 | 21 | var state = { 22 | moving: false 23 | }; 24 | log("Stopping device movement."); 25 | updateState(state); 26 | 27 | log("'StopMovingDevice' JavaScript method simulation completed."); 28 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/truck-01-protobuf.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "truck-01-proto", 4 | "Version": "0.0.1", 5 | "Name": "Protobuf Truck", 6 | "Description": "Truck with GPS, speed and cargo temperature sensors", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "latitude": 47.445301, 12 | "longitude": -122.296307, 13 | "speed": 30.0, 14 | "speed_unit": "mph", 15 | "temperature": 38.0, 16 | "temperature_unit": "F" 17 | }, 18 | "Interval": "00:00:10", 19 | "Scripts": [ 20 | { 21 | "Type": "javascript", 22 | "Path": "truck-01-state.js", 23 | "Params": { 24 | "TelemetryType": "Truck;v1" 25 | } 26 | } 27 | ] 28 | }, 29 | "Properties": { 30 | "Type": "Truck01Proto", 31 | "Location": "Field", 32 | "Latitude": 47.445301, 33 | "Longitude": -122.296307 34 | }, 35 | "Telemetry": [ 36 | { 37 | "Interval": "00:00:10", 38 | "MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude},\"speed\": ${speed},\"speed_unit\":\"${speed_unit}\",\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}", 39 | "MessageSchema": { 40 | "Name": "Truck;v1", 41 | "ClassName": "Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Protobuf.Truck", 42 | "Format": "Protobuf", 43 | "Fields": { 44 | "latitude": "double", 45 | "longitude": "double", 46 | "speed": "double", 47 | "speed_unit": "text", 48 | "temperature": "double", 49 | "temperature_unit": "text" 50 | } 51 | } 52 | } 53 | ], 54 | "CloudToDeviceMethods": { 55 | "FirmwareUpdate": { 56 | "Type": "javascript", 57 | "Path": "FirmwareUpdate-method.js" 58 | }, 59 | "DecreaseCargoTemperature": { 60 | "Type": "javascript", 61 | "Path": "TempDecrease-method.js" 62 | }, 63 | "IncreaseCargoTemperature": { 64 | "Type": "javascript", 65 | "Path": "TempIncrease-method.js" 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/truck-01.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "truck-01", 4 | "Version": "0.0.1", 5 | "Name": "Truck", 6 | "Description": "Truck with GPS, speed and cargo temperature sensors", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "latitude": 47.445301, 12 | "longitude": -122.296307, 13 | "speed": 30.0, 14 | "speed_unit": "mph", 15 | "temperature": 38.0, 16 | "temperature_unit": "F" 17 | }, 18 | "Interval": "00:00:10", 19 | "Scripts": [ 20 | { 21 | "Type": "javascript", 22 | "Path": "truck-01-state.js" 23 | } 24 | ] 25 | }, 26 | "Properties": { 27 | "Type": "Truck", 28 | "Location": "Field", 29 | "Latitude": 47.445301, 30 | "Longitude": -122.296307 31 | }, 32 | "Telemetry": [ 33 | { 34 | "Interval": "00:00:10", 35 | "MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude},\"speed\": ${speed},\"speed_unit\":\"${speed_unit}\",\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}", 36 | "MessageSchema": { 37 | "Name": "truck-sensors;v1", 38 | "Format": "JSON", 39 | "Fields": { 40 | "latitude": "double", 41 | "longitude": "double", 42 | "speed": "double", 43 | "speed_unit": "text", 44 | "temperature": "double", 45 | "temperature_unit": "text" 46 | } 47 | } 48 | } 49 | ], 50 | "CloudToDeviceMethods": { 51 | "FirmwareUpdate": { 52 | "Type": "javascript", 53 | "Path": "FirmwareUpdate-method.js" 54 | }, 55 | "DecreaseCargoTemperature": { 56 | "Type": "javascript", 57 | "Path": "TempDecrease-method.js" 58 | }, 59 | "IncreaseCargoTemperature": { 60 | "Type": "javascript", 61 | "Path": "TempIncrease-method.js" 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Services/data/devicemodels/truck-02.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "truck-02", 4 | "Version": "0.0.1", 5 | "Name": "Faulty Truck", 6 | "Description": "Truck with GPS, speed and cargo temperature sensors. Temperature is higher than normal.", 7 | "Protocol": "AMQP", 8 | "Simulation": { 9 | "InitialState": { 10 | "online": true, 11 | "latitude": 47.25433, 12 | "longitude": -121.177075, 13 | "speed": 30.0, 14 | "speed_unit": "mph", 15 | "temperature": 49.0, 16 | "temperature_unit": "F" 17 | }, 18 | "Interval": "00:00:10", 19 | "Scripts": [ 20 | { 21 | "Type": "javascript", 22 | "Path": "truck-02-state.js" 23 | } 24 | ] 25 | }, 26 | "Properties": { 27 | "Type": "Truck", 28 | "Location": "Field", 29 | "Latitude": 47.25433, 30 | "Longitude": -121.177075 31 | }, 32 | "Telemetry": [ 33 | { 34 | "Interval": "00:00:10", 35 | "MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude},\"speed\": ${speed},\"speed_unit\":\"${speed_unit}\",\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}", 36 | "MessageSchema": { 37 | "Name": "truck-sensors;v1", 38 | "Format": "JSON", 39 | "Fields": { 40 | "latitude": "double", 41 | "longitude": "double", 42 | "speed": "double", 43 | "speed_unit": "text", 44 | "temperature": "double", 45 | "temperature_unit": "text" 46 | } 47 | } 48 | } 49 | ], 50 | "CloudToDeviceMethods": { 51 | "FirmwareUpdate": { 52 | "Type": "javascript", 53 | "Path": "FirmwareUpdate-method.js" 54 | }, 55 | "DecreaseCargoTemperature": { 56 | "Type": "javascript", 57 | "Path": "TempDecrease-method.js" 58 | }, 59 | "IncreaseCargoTemperature": { 60 | "Type": "javascript", 61 | "Path": "TempIncrease-method.js" 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Services/data/iothub/README.md: -------------------------------------------------------------------------------- 1 | This folder is used at run time to store a custom data, like the custom connection string. 2 | 3 | Do not delete this folder, and make sure the configuration points here. -------------------------------------------------------------------------------- /Services/data/replayfile/replay.json: -------------------------------------------------------------------------------- 1 | { 2 | "SchemaVersion": "1.0.0", 3 | "Id": "replay", 4 | "Version": "0.0.1", 5 | "Name": "Replay", 6 | "Description": "Fake device model for csv file replay", 7 | "Protocol": "AMQP", 8 | "ReplayFile": "", 9 | "Simulation": { 10 | "InitialState": { 11 | "online": true 12 | }, 13 | "Interval": "00:00:00", 14 | "Scripts": [ 15 | ] 16 | }, 17 | "Properties": { 18 | }, 19 | "Tags": { 20 | }, 21 | "Telemetry": [ 22 | { 23 | "MessageTemplate": "", 24 | "MessageSchema": { 25 | "Name": "replay-sensors;v1", 26 | "Format": "JSON", 27 | "Fields": { 28 | } 29 | } 30 | } 31 | ], 32 | "CloudToDeviceMethods": { 33 | } 34 | } -------------------------------------------------------------------------------- /Services/data/replayfile/simulationReplayTest.csv: -------------------------------------------------------------------------------- 1 | telemetry, 00:00:00,{"temperature": 51.32, "temperature_unit": "fahrenheit", "humidity": 69.59, "humidity_unit":"RH", "pressure": 440.20, "pressure_unit": "psi"} 2 | telemetry, 00:00:30,{"temperature": 71.32, "temperature_unit": "fahrenheit", "humidity": 79.59, "humidity_unit":"RH", "pressure": 240.20, "pressure_unit": "psi"} 3 | telemetry, 00:01:00,{"temperature": 30.00, "temperature_unit": "fahrenheit", "humidity": 59.59, "humidity_unit":"RH", "pressure": 340.20, "pressure_unit": "psi"} -------------------------------------------------------------------------------- /Services/data/templates/multiple-simulations-template.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "Sample Multi-Model Simulation", 4 | "Description": "A sample simulation with multiple device types", 5 | "Enabled": false, 6 | "IotHubConnectionStrings": [ 7 | "default" 8 | ], 9 | "DeviceModels": [ 10 | { 11 | "Id": "chiller-01", 12 | "Count": 50 13 | }, 14 | { 15 | "Id": "elevator-01", 16 | "Count": 10 17 | } 18 | ], 19 | "RateLimits": { 20 | "RegistryOperationsPerMinute": 100, 21 | "TwinReadsPerSecond": 10, 22 | "TwinWritesPerSecond": 10, 23 | "ConnectionsPerSecond": 120, 24 | "DeviceMessagesPerSecond": 120 25 | } 26 | }, 27 | { 28 | "Name": "Sample Simple Simulation", 29 | "Description": "An example showing 10 simulated trucks", 30 | "Enabled": false, 31 | "IotHubConnectionStrings": [ 32 | "default" 33 | ], 34 | "DeviceModels": [ 35 | { 36 | "Id": "truck-01", 37 | "Count": 10 38 | } 39 | ], 40 | "RateLimits": { 41 | "RegistryOperationsPerMinute": 100, 42 | "TwinReadsPerSecond": 10, 43 | "TwinWritesPerSecond": 10, 44 | "ConnectionsPerSecond": 120, 45 | "DeviceMessagesPerSecond": 120 46 | } 47 | } 48 | ] -------------------------------------------------------------------------------- /Services/data/templates/remote-monitoring-simulations-template.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name": "Remote Monitoring Multi-Model Simulation", 4 | "Description": "A simulation with multiple device types for Remote Monitoring", 5 | "Enabled": true, 6 | "IotHubConnectionStrings": [ 7 | "default" 8 | ], 9 | "DeviceModels": [ 10 | { 11 | "Id": "chiller-01", 12 | "Count": 1 13 | }, 14 | { 15 | "Id": "chiller-02", 16 | "Count": 1 17 | }, 18 | { 19 | "Id": "elevator-01", 20 | "Count": 1 21 | }, 22 | { 23 | "Id": "elevator-02", 24 | "Count": 1 25 | }, 26 | { 27 | "Id": "engine-01", 28 | "Count": 1 29 | }, 30 | { 31 | "Id": "engine-02", 32 | "Count": 1 33 | }, 34 | { 35 | "Id": "prototype-01", 36 | "Count": 1 37 | }, 38 | { 39 | "Id": "prototype-02", 40 | "Count": 1 41 | }, 42 | { 43 | "Id": "truck-01", 44 | "Count": 1 45 | }, 46 | { 47 | "Id": "truck-02", 48 | "Count": 1 49 | }, 50 | { 51 | "Id": "delivery-truck-01", 52 | "Count": 1 53 | }, 54 | { 55 | "Id": "delivery-truck-02", 56 | "Count": 1 57 | } 58 | ], 59 | "RateLimits": { 60 | "RegistryOperationsPerMinute": 100, 61 | "TwinReadsPerSecond": 10, 62 | "TwinWritesPerSecond": 10, 63 | "ConnectionsPerSecond": 120, 64 | "DeviceMessagesPerSecond": 120 65 | } 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /SimulationAgent.Test/SimulationAgent.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.1 4 | latest 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /SimulationAgent.Test/helpers/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace SimulationAgent.Test.helpers 4 | { 5 | /// 6 | /// Use these flags to allow running a subset of tests from the test 7 | /// explorer and the command line. 8 | /// 9 | public class Constants 10 | { 11 | // Use this to kill unit tests not supposed to run async code 12 | public const int TEST_TIMEOUT = 5000; 13 | 14 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 15 | public const string TYPE = "Type"; 16 | public const string UNIT_TEST = "UnitTest"; 17 | public const string INTEGRATION_TEST = "IntegrationTest"; 18 | 19 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 20 | public const string SPEED = "Speed"; 21 | public const string SLOW_TEST = "SlowTest"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SimulationAgent.Test/helpers/Http/HttpRequestOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace SimulationAgent.Test.helpers.Http 4 | { 5 | public class HttpRequestOptions 6 | { 7 | public bool EnsureSuccess { get; set; } = false; 8 | 9 | public bool AllowInsecureSSLServer { get; set; } = false; 10 | 11 | public int Timeout { get; set; } = 30000; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SimulationAgent.Test/helpers/Http/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Net; 4 | using System.Net.Http.Headers; 5 | 6 | namespace SimulationAgent.Test.helpers.Http 7 | { 8 | public interface IHttpResponse 9 | { 10 | HttpStatusCode StatusCode { get; } 11 | HttpResponseHeaders Headers { get; } 12 | string Content { get; } 13 | bool IsRetriableError { get; } 14 | } 15 | 16 | public class HttpResponse : IHttpResponse 17 | { 18 | private const int TOO_MANY_REQUESTS = 429; 19 | 20 | public HttpResponse() 21 | { 22 | } 23 | 24 | public HttpResponse( 25 | HttpStatusCode statusCode, 26 | string content, 27 | HttpResponseHeaders headers) 28 | { 29 | this.StatusCode = statusCode; 30 | this.Headers = headers; 31 | this.Content = content; 32 | } 33 | 34 | public HttpStatusCode StatusCode { get; internal set; } 35 | public HttpResponseHeaders Headers { get; internal set; } 36 | public string Content { get; internal set; } 37 | 38 | public bool IsRetriableError => this.StatusCode == HttpStatusCode.NotFound || 39 | this.StatusCode == HttpStatusCode.RequestTimeout || 40 | (int) this.StatusCode == TOO_MANY_REQUESTS; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SimulationAgent.Test/helpers/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Xunit.Sdk; 5 | 6 | namespace SimulationAgent.Test.helpers 7 | { 8 | /** 9 | * Use this class when testing asynchronous code, to avoid tests 10 | * running forever, e.g. in case threads don't end as expected. 11 | * 12 | * Example: 13 | * 14 | * this.target.SomeMethodAsync().CompleteOrTimeout(); 15 | * 16 | * var result = this.target.SomeMethodAsync().CompleteOrTimeout().Result; 17 | */ 18 | public static class TaskExtensions 19 | { 20 | // Wait for the task to complete or timeout 21 | public static Task CompleteOrTimeout(this Task t) 22 | { 23 | var complete = t.Wait(Constants.TEST_TIMEOUT); 24 | if (!complete) 25 | { 26 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 27 | } 28 | 29 | return t; 30 | } 31 | 32 | // Wait for the task to complete or timeout 33 | public static Task CompleteOrTimeout(this Task t) 34 | { 35 | var complete = t.Wait(Constants.TEST_TIMEOUT); 36 | if (!complete) 37 | { 38 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 39 | } 40 | 41 | return t; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SimulationAgent/DeviceConnection/CredentialsSetup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.DataStructures; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; 6 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 7 | 8 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection 9 | { 10 | /// 11 | /// Prepare the device credentials using known settings from the local configuration 12 | /// 13 | public class CredentialsSetup : IDeviceConnectionLogic 14 | { 15 | private readonly ILogger log; 16 | private readonly IInstance instance; 17 | 18 | private string deviceId; 19 | private IDeviceConnectionActor deviceContext; 20 | private ISimulationContext simulationContext; 21 | 22 | public CredentialsSetup(ILogger logger, IInstance instance) 23 | { 24 | this.log = logger; 25 | this.instance = instance; 26 | } 27 | 28 | public void Init(IDeviceConnectionActor context, string deviceId, DeviceModel deviceModel) 29 | { 30 | this.instance.InitOnce(); 31 | 32 | this.deviceContext = context; 33 | this.simulationContext = context.SimulationContext; 34 | this.deviceId = deviceId; 35 | 36 | this.instance.InitComplete(); 37 | } 38 | 39 | public Task RunAsync() 40 | { 41 | this.instance.InitRequired(); 42 | 43 | this.log.Debug("Configuring device credentials...", () => new { this.deviceId }); 44 | this.deviceContext.Device = this.simulationContext.Devices.GetWithKnownCredentials(this.deviceId); 45 | this.deviceContext.HandleEvent(DeviceConnectionActor.ActorEvents.CredentialsSetupCompleted); 46 | return Task.CompletedTask; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SimulationAgent/DeviceConnection/Disconnect.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.DataStructures; 6 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; 7 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 8 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation; 9 | 10 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection 11 | { 12 | /// 13 | /// Disconnect from Azure IoT Hub 14 | /// 15 | public class Disconnect : IDeviceConnectionLogic 16 | { 17 | private readonly ILogger log; 18 | private readonly IInstance instance; 19 | private string deviceId; 20 | private IDeviceConnectionActor deviceContext; 21 | 22 | public Disconnect( 23 | ILogger logger, 24 | IInstance instance) 25 | { 26 | this.log = logger; 27 | this.instance = instance; 28 | } 29 | 30 | public void Init(IDeviceConnectionActor context, string deviceId, DeviceModel deviceModel) 31 | { 32 | this.instance.InitOnce(); 33 | 34 | this.deviceContext = context; 35 | this.deviceId = deviceId; 36 | 37 | this.instance.InitComplete(); 38 | } 39 | 40 | public async Task RunAsync() 41 | { 42 | this.instance.InitRequired(); 43 | 44 | this.log.Debug("Disconnecting...", () => new { this.deviceId }); 45 | 46 | try 47 | { 48 | await this.deviceContext.Client.DisconnectAsync(); 49 | 50 | this.log.Debug("Device disconnected", () => new { this.deviceId }); 51 | this.deviceContext.HandleEvent(DeviceConnectionActor.ActorEvents.Disconnected); 52 | } 53 | catch (Exception e) 54 | { 55 | this.log.Error("Error disconnecting device", () => new { this.deviceId, e }); 56 | this.deviceContext.HandleEvent(DeviceConnectionActor.ActorEvents.DisconnectionFailed); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SimulationAgent/DeviceConnection/IDeviceConnectionLogic.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection 7 | { 8 | public interface IDeviceConnectionLogic 9 | { 10 | void Init(IDeviceConnectionActor context, string deviceId, DeviceModel deviceModel); 11 | Task RunAsync(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SimulationAgent/DeviceProperties/IDevicePropertiesLogic.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceProperties 7 | { 8 | public interface IDevicePropertiesLogic 9 | { 10 | void Init(IDevicePropertiesActor devicePropertiesActor, string deviceId, IDevices devices); 11 | Task RunAsync(); 12 | } 13 | } -------------------------------------------------------------------------------- /SimulationAgent/DeviceTelemetry/IDeviceTelemetryLogic.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceTelemetry 7 | { 8 | public interface IDeviceTelemetryLogic 9 | { 10 | void Init(IDeviceTelemetryActor deviceTelemetryActor, string deviceId, DeviceModel deviceModel); 11 | Task RunAsync(); 12 | } 13 | } -------------------------------------------------------------------------------- /SimulationAgent/SimulationAgent.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.1 4 | latest 5 | Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent 6 | Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /WebService.Test/IntegrationTests/README.md: -------------------------------------------------------------------------------- 1 | Integration Tests 2 | ================= 3 | 4 | ## Guidelines 5 | 6 | 7 | 8 | ## Conventions 9 | 10 | * For each scenario create a test class with "Test" suffix. 11 | * Flag all the tests with `[Fact, Trait(Constants.Type, Constants.IntegrationTest)]` 12 | -------------------------------------------------------------------------------- /WebService.Test/README.md: -------------------------------------------------------------------------------- 1 | Unit Tests and Integration Tests 2 | ================================ 3 | 4 | ## Guidelines 5 | 6 | 7 | 8 | ## Conventions 9 | 10 | * For each class create a test class with "Test" suffix. 11 | * Flag all the tests with a type, e.g. `[Fact, Trait(Constants.Type, Constants.UnitTest)]` 12 | * Store Integration Tests under `IntegrationTests/` and use 13 | the `[Fact, Trait(Constants.Type, Constants.IntegrationTest)]` attribute 14 | -------------------------------------------------------------------------------- /WebService.Test/WebService.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp3.1 4 | latest 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /WebService.Test/helpers/Constants.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace WebService.Test.helpers 4 | { 5 | public static class Constants 6 | { 7 | // Use this to kill unit tests not supposed to run async code 8 | public const int TEST_TIMEOUT = 5000; 9 | 10 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 11 | public const string TYPE = "Type"; 12 | public const string UNIT_TEST = "UnitTest"; 13 | public const string INTEGRATION_TEST = "IntegrationTest"; 14 | 15 | // Use these flags to allow running a subset of tests from the test explorer and the command line. 16 | public const string SPEED = "Speed"; 17 | public const string SLOW_TEST = "SlowTest"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebService.Test/helpers/Http/HttpRequestOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace WebService.Test.helpers.Http 4 | { 5 | public class HttpRequestOptions 6 | { 7 | public bool EnsureSuccess { get; set; } = false; 8 | 9 | public bool AllowInsecureSSLServer { get; set; } = false; 10 | 11 | public int Timeout { get; set; } = 30000; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /WebService.Test/helpers/Http/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Net; 4 | using System.Net.Http.Headers; 5 | 6 | namespace WebService.Test.helpers.Http 7 | { 8 | public interface IHttpResponse 9 | { 10 | HttpStatusCode StatusCode { get; } 11 | HttpResponseHeaders Headers { get; } 12 | string Content { get; } 13 | bool IsRetriableError { get; } 14 | } 15 | 16 | public class HttpResponse : IHttpResponse 17 | { 18 | private const int TOO_MANY_REQUESTS = 429; 19 | 20 | public HttpResponse() 21 | { 22 | } 23 | 24 | public HttpResponse( 25 | HttpStatusCode statusCode, 26 | string content, 27 | HttpResponseHeaders headers) 28 | { 29 | this.StatusCode = statusCode; 30 | this.Headers = headers; 31 | this.Content = content; 32 | } 33 | 34 | public HttpStatusCode StatusCode { get; internal set; } 35 | public HttpResponseHeaders Headers { get; internal set; } 36 | public string Content { get; internal set; } 37 | 38 | public bool IsRetriableError => this.StatusCode == HttpStatusCode.NotFound || 39 | this.StatusCode == HttpStatusCode.RequestTimeout || 40 | (int) this.StatusCode == TOO_MANY_REQUESTS; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WebService.Test/helpers/SomeException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace WebService.Test.helpers 6 | { 7 | /// 8 | /// This class is used to inject exceptions in the code under test 9 | /// and to verify that the system fails with the injected exception, 10 | /// i.e. not any exception, to be sure unit tests wouldn't pass in case 11 | /// a different exception is occurring, i.e. to avoid false positives. 12 | /// 13 | public class SomeException : Exception 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebService.Test/helpers/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Xunit.Sdk; 5 | 6 | namespace WebService.Test.helpers 7 | { 8 | /** 9 | * Use this class when testing asynchronous code, to avoid tests 10 | * running forever, e.g. in case threads don't end as expected. 11 | * 12 | * Example: 13 | * 14 | * this.target.SomeMethodAsync().CompleteOrTimeout(); 15 | * 16 | * var result = this.target.SomeMethodAsync().CompleteOrTimeout().Result; 17 | */ 18 | public static class TaskExtensions 19 | { 20 | // Wait for the task to complete or timeout 21 | public static Task CompleteOrTimeout(this Task t) 22 | { 23 | var complete = t.Wait(Constants.TEST_TIMEOUT); 24 | if (!complete) 25 | { 26 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 27 | } 28 | 29 | return t; 30 | } 31 | 32 | // Wait for the task to complete or timeout 33 | public static Task CompleteOrTimeout(this Task t) 34 | { 35 | var complete = t.Wait(Constants.TEST_TIMEOUT); 36 | if (!complete) 37 | { 38 | throw new TestTimeoutException(Constants.TEST_TIMEOUT); 39 | } 40 | 41 | return t; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /WebService.Test/helpers/WebServiceHost.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace WebService.Test.helpers 6 | { 7 | public class WebServiceHost 8 | { 9 | public static string GetBaseAddress() 10 | { 11 | int port = new Random().Next(40000, 60000); 12 | string baseAddress = "http://127.0.0.1:" + port; 13 | return baseAddress; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebService.Test/v1/Controllers/DeviceModelPropertiesControllerTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers; 6 | using Moq; 7 | using WebService.Test.helpers; 8 | using Xunit; 9 | 10 | namespace WebService.Test.v1.Controllers 11 | { 12 | public class DeviceModelPropertiesControllerTest 13 | { 14 | private readonly Mock deviceModelsService; 15 | private readonly DeviceModelPropertiesController target; 16 | 17 | public DeviceModelPropertiesControllerTest() 18 | { 19 | this.deviceModelsService = new Mock(); 20 | this.target = new DeviceModelPropertiesController(this.deviceModelsService.Object); 21 | } 22 | 23 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 24 | public void ItReturnsTheListOfPropertyNames() 25 | { 26 | // Arrange 27 | var properties = new List 28 | { 29 | "Type", 30 | "Firmware", 31 | "Location", 32 | "Model", 33 | "Latitude", 34 | "Longitude" 35 | }; 36 | 37 | this.deviceModelsService 38 | .Setup(x => x.GetPropertyNamesAsync()) 39 | .ReturnsAsync(properties); 40 | 41 | // Act 42 | var result = this.target.GetAsync().Result; 43 | 44 | // Assert 45 | Assert.Equal(properties.Count, result.Items.Count); 46 | foreach (var resultItem in result.Items) 47 | { 48 | Assert.Contains(resultItem, properties); 49 | } 50 | } 51 | 52 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 53 | public void ItThrowsExceptionWhenGetPropertyNamesFailed() 54 | { 55 | // Arrange 56 | this.deviceModelsService 57 | .Setup(x => x.GetPropertyNamesAsync()) 58 | .ThrowsAsync(new SomeException()); 59 | 60 | // Act & Assert 61 | Assert.ThrowsAsync( 62 | async () => await this.target.GetAsync()) 63 | .Wait(Constants.TEST_TIMEOUT); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /WebService.Test/v1/Models/SimulationApiModel/DeviceModelSimulationScriptOverrideTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 6 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.SimulationApiModel; 7 | using WebService.Test.helpers; 8 | using Xunit; 9 | 10 | namespace WebService.Test.v1.Models.SimulationApiModel 11 | { 12 | public class DeviceModelScriptOverrideTest 13 | { 14 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 15 | public void ItReturnsDeviceModelScriptApiModelOverrideFromServiceModel() 16 | { 17 | // Arrange 18 | var scriptOverride = this.GetScriptOverride(); 19 | 20 | // Act 21 | var result = DeviceModeScriptOverride.FromServiceModel(scriptOverride); 22 | 23 | // Assert 24 | Assert.IsType(result.FirstOrDefault()); 25 | } 26 | 27 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 28 | public void ItReturnsDeviceModelSimulationScriptOverrideFromDeviceModelSimulationScriptApiModel() 29 | { 30 | // Arrange 31 | var scriptApiModelOverride = this.GetDeviceModelScriptApiModelOverride(); 32 | 33 | // Act 34 | var result = scriptApiModelOverride.ToServiceModel(); 35 | 36 | // Assert 37 | Assert.IsType(result); 38 | } 39 | 40 | private DeviceModeScriptOverride GetDeviceModelScriptApiModelOverride() 41 | { 42 | var scriptOverride = new DeviceModeScriptOverride() 43 | { 44 | Type = "", 45 | Path = "", 46 | Params = "" 47 | }; 48 | 49 | return scriptOverride; 50 | } 51 | 52 | private IList GetScriptOverride() 53 | { 54 | var scriptOverride = new List() 55 | { 56 | new Simulation.DeviceModelScriptOverride() 57 | { 58 | Type = "", 59 | Path = "", 60 | Params = "" 61 | } 62 | }; 63 | 64 | return scriptOverride; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /WebService.Test/v1/Models/SimulationApiModel/MetricsApiModelTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.AzureManagementAdapter; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.SimulationApiModel; 6 | using WebService.Test.helpers; 7 | using Xunit; 8 | 9 | namespace WebService.Test.v1.Models.SimulationApiModel 10 | { 11 | public class MetricsApiModelTest 12 | { 13 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 14 | public void ItReturnsMetricsRequestsModelFromApiModel() 15 | { 16 | // Arrange 17 | var requests = new List 18 | { 19 | new MetricsRequestApiModel() 20 | }; 21 | 22 | var apiModel = new MetricsRequestsApiModel 23 | { 24 | Requests = requests 25 | }; 26 | 27 | // Act 28 | var result = apiModel.ToServiceModel(); 29 | 30 | // Assert 31 | Assert.IsType(result); 32 | Assert.Equal(requests.Count, result.Requests.Count); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /WebService.Test/v1/Models/SimulationApiModel/SimulationDeviceModelRefTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 6 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.SimulationApiModel; 7 | using WebService.Test.helpers; 8 | using Xunit; 9 | 10 | namespace WebService.Test.v1.Models.SimulationApiModel 11 | { 12 | public class SimulationDeviceModelRefTest 13 | { 14 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 15 | public void ItReturnsAListOfDeviceModelRefFromServiceModel() 16 | { 17 | // Arrange 18 | var deviceModelRefs = this.GetDeviceModelRefs(); 19 | 20 | // Act 21 | var result = SimulationDeviceModelRef.FromServiceModel(deviceModelRefs); 22 | 23 | // Assert 24 | Assert.IsType(result.FirstOrDefault()); 25 | } 26 | 27 | [Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)] 28 | public void ItReturnsDeviceModelRefFromApiModel() 29 | { 30 | // Arrange 31 | var deviceModelRef = this.GetDeviceModelRef(); 32 | 33 | // Act 34 | var result = deviceModelRef.ToServiceModel(); 35 | 36 | // Assert 37 | Assert.IsType(result); 38 | } 39 | 40 | private SimulationDeviceModelRef GetDeviceModelRef() 41 | { 42 | var deviceModelRef = new SimulationDeviceModelRef(); 43 | 44 | return deviceModelRef; 45 | } 46 | 47 | private IEnumerable GetDeviceModelRefs() 48 | { 49 | var deviceModelRef = new List() 50 | { 51 | new Simulation.DeviceModelRef() 52 | }; 53 | 54 | return deviceModelRef; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /WebService/Auth/ClientAuthConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth 7 | { 8 | public interface IClientAuthConfig 9 | { 10 | // CORS whitelist, in form { "origins": [], "methods": [], "headers": [] } 11 | // Defaults to empty, meaning No CORS. 12 | string CorsWhitelist { get; set; } 13 | 14 | // Whether CORS support is enabled 15 | // Default: false 16 | bool CorsEnabled { get; } 17 | 18 | // Whether the authentication and authorization is required or optional. 19 | // Default: true 20 | bool AuthRequired { get; set; } 21 | 22 | // Auth type: currently supports only "JWT" 23 | // Default: JWT 24 | string AuthType { get; set; } 25 | 26 | // The list of allowed signing algoritms 27 | // Default: RS256, RS384, RS512 28 | IEnumerable JwtAllowedAlgos { get; set; } 29 | 30 | // The trusted issuer 31 | string JwtIssuer { get; set; } 32 | 33 | // The required audience 34 | string JwtAudience { get; set; } 35 | 36 | // Clock skew allowed when validating tokens expiration 37 | // Default: 2 minutes 38 | TimeSpan JwtClockSkew { get; set; } 39 | } 40 | 41 | public class ClientAuthConfig : IClientAuthConfig 42 | { 43 | public string CorsWhitelist { get; set; } 44 | public bool CorsEnabled => !string.IsNullOrEmpty(this.CorsWhitelist.Trim()); 45 | public bool AuthRequired { get; set; } 46 | public string AuthType { get; set; } 47 | public IEnumerable JwtAllowedAlgos { get; set; } 48 | public string JwtIssuer { get; set; } 49 | public string JwtAudience { get; set; } 50 | public TimeSpan JwtClockSkew { get; set; } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /WebService/Auth/CorsWhitelistModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth 4 | { 5 | class CorsWhitelistModel 6 | { 7 | public string[] Origins { get; set; } 8 | public string[] Methods { get; set; } 9 | public string[] Headers { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /WebService/Auth/RequestExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using System.Security.Claims; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth 8 | { 9 | public static class RequestExtension 10 | { 11 | private const string CONTEXT_KEY = "CurrentUserClaims"; 12 | 13 | // Store the current user claims in the current request 14 | public static void SetCurrentUserClaims(this HttpRequest request, IEnumerable claims) 15 | { 16 | request.HttpContext.Items[CONTEXT_KEY] = claims; 17 | } 18 | 19 | // Get the user claims from the current request 20 | public static IEnumerable GetCurrentUserClaims(this HttpRequest request) 21 | { 22 | if (!request.HttpContext.Items.ContainsKey(CONTEXT_KEY)) 23 | { 24 | return new List(); 25 | } 26 | 27 | return request.HttpContext.Items[CONTEXT_KEY] as IEnumerable; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /WebService/Auth/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Autofac; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime; 6 | using Microsoft.IdentityModel.Protocols; 7 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 8 | 9 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Auth 10 | { 11 | public class Startup 12 | { 13 | public static void SetupDependencies(ContainerBuilder builder, IConfig config) 14 | { 15 | builder.RegisterInstance(config.ClientAuthConfig).As().SingleInstance(); 16 | builder.RegisterType().As().SingleInstance(); 17 | 18 | // OpenId Connect manager 19 | builder.RegisterInstance(GetOpenIdConnectManager(config)) 20 | .As>().SingleInstance(); 21 | } 22 | 23 | // Prepare the OpenId Connect configuration manager, responsibile 24 | // for retrieving the JWT signing keys and cache them in memory. 25 | // See: https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.4 26 | private static IConfigurationManager GetOpenIdConnectManager(IConfig config) 27 | { 28 | // Avoid starting the real OpenId Connect manager if not needed, which would 29 | // start throwing errors when attempting to fetch certificates. 30 | if (!config.ClientAuthConfig.AuthRequired) 31 | { 32 | return new StaticConfigurationManager( 33 | new OpenIdConnectConfiguration()); 34 | } 35 | 36 | return new ConfigurationManager( 37 | config.ClientAuthConfig.JwtIssuer + ".well-known/openid-configuration", 38 | new OpenIdConnectConfigurationRetriever()) 39 | { 40 | // How often the list of keys in memory is refreshed. Default is 24 hours. 41 | AutomaticRefreshInterval = TimeSpan.FromHours(6), 42 | 43 | // The minimum time between retrievals, in the event that a retrieval 44 | // failed, or that a refresh is explicitly requested. Default is 30 seconds. 45 | RefreshInterval = TimeSpan.FromMinutes(1) 46 | }; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /WebService/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.IO; 5 | using Microsoft.AspNetCore.Connections; 6 | using Microsoft.AspNetCore.Hosting; 7 | 8 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService 9 | { 10 | public static class Program 11 | { 12 | // Application entry point. This is where we set up the web host (Kestrel) 13 | // that will handle HTTP requests 14 | public static void Main(string[] args) 15 | { 16 | var webServicePort = DependencyResolution.GetConfig().Port; 17 | 18 | try 19 | { 20 | /* 21 | Kestrel is a cross-platform HTTP server based on libuv, 22 | a cross-platform asynchronous I/O library. 23 | https://docs.microsoft.com/aspnet/core/fundamentals/servers 24 | */ 25 | // TODO: Use HostBuilder instead of WebHostBuilder? https://docs.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.1&tabs=visual-studio#kestrel 26 | var host = new WebHostBuilder() 27 | .UseUrls("http://*:" + webServicePort) 28 | .UseKestrel(options => { options.AddServerHeader = false; }) 29 | .UseIISIntegration() 30 | .UseStartup() // <- ASP.Net Core will call in to Startup to set up the middleware 31 | .Build(); 32 | 33 | host.Run(); 34 | } 35 | catch (IOException e) 36 | when (e.InnerException is AddressInUseException) 37 | { 38 | PrintTcpErrorMessage(webServicePort); 39 | // Required to kill the process 40 | throw; 41 | } 42 | } 43 | 44 | private static void PrintTcpErrorMessage(int port) 45 | { 46 | Console.BackgroundColor = ConsoleColor.DarkRed; 47 | Console.ForegroundColor = ConsoleColor.White; 48 | Console.WriteLine("Fatal error, the port " + port + " required by the web service is not available."); 49 | Console.ResetColor(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /WebService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "WebService": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "launchUrl": "http://localhost:9003/v1/status", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development", 9 | "PCS_IOTHUB_CONNSTRING": "your Azure IoT Hub connection string", 10 | "PCS_STORAGEADAPTER_DOCUMENTDB_CONNSTRING": "your Cosmos DB SQL connection string", 11 | "PCS_AZURE_STORAGE_ACCOUNT": "your Azure Storage Account connection string", 12 | "PCS_STORAGEADAPTER_WEBSERVICE_URL": "http://localhost:9022/v1", 13 | "PCS_RESOURCE_GROUP_LOCATION": "your Azure resource group location", 14 | "PCS_VMSS_NAME": "your vm scale set name", 15 | "PCS_LOG_LEVEL": "Debug", 16 | "PCS_AUTH_REQUIRED": "false", 17 | "PCS_AUTH_ISSUER": "", 18 | "PCS_AUTH_AUDIENCE": "", 19 | "PCS_CORS_WHITELIST": "", 20 | "PCS_SUBSCRIPTION_DOMAIN": "", 21 | "PCS_SUBSCRIPTION_ID": "", 22 | "PCS_RESOURCE_GROUP": "", 23 | "PCS_IOHUB_NAME": "", 24 | "PCS_SEED_TEMPLATE": "" 25 | }, 26 | "applicationUrl": "http://localhost:9003/v1/status" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /WebService/README.md: -------------------------------------------------------------------------------- 1 | Web service 2 | =========== 3 | 4 | ## ASP.NET Web API and Kestrel 5 | 6 | The web service is built on ASP.NET Web API and hosted via Kestrel, i.e. IIS is 7 | not strictly required to run the service, although it would be possible if required. 8 | More information can be found here: 9 | 10 | * [Building Web APIs](https://docs.microsoft.com/aspnet/core/mvc/web-api) 11 | * [Routing in ASP.NET Core](https://docs.microsoft.com/aspnet/core/fundamentals/routing) 12 | * [Introduction to Kestrel web server implementation in ASP.NET Core](https://docs.microsoft.com/aspnet/core/fundamentals/servers/kestrel) 13 | 14 | ## Guidelines 15 | 16 | The web service is the microservice entry point. There might be other 17 | entry points if the microservice has some background agent, for instance to run 18 | continuous tasks like log aggregation, simulations, watchdogs etc. 19 | 20 | The web service takes care of loading the configuration, and injecting it to 21 | underlying dependencies, like the service layer. Most of the business logic 22 | is encapsulated in the service layer, while the web service has the 23 | responsibility of accepting requests and providing responses in the correct 24 | format. 25 | 26 | ## Conventions 27 | 28 | * Web service routing is defined by convention, e.g. the name of the controllers 29 | defines the supported paths. 30 | * The microservice configuration is defined in the `appsettings.ini` file 31 | stored in the `WebService` project 32 | -------------------------------------------------------------------------------- /WebService/Runtime/ConfigFile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.IO; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime 7 | { 8 | public static class ConfigFile 9 | { 10 | public const string DEFAULT = "appsettings.ini"; 11 | private const string DEV_ENV_VAR = "PCS_DEV_APPSETTINGS"; 12 | 13 | // If the system has a "PCS_DEV_APPSETTINGS" environment variable, and the 14 | // value references an existing file, then the app will use this file to pull in settings, 15 | // before looking into "appsettings.ini". Use this mechanism to override the default 16 | // configuration with your values during development. Do not use this approach 17 | // in production. Also, make sure your dev config settings are not checked into 18 | // the repository or the Docker image. 19 | public static string GetDevOnlyConfigFile() 20 | { 21 | try 22 | { 23 | string devConfigFile = Environment.GetEnvironmentVariable(DEV_ENV_VAR); 24 | if (!string.IsNullOrEmpty(devConfigFile) && File.Exists(devConfigFile)) 25 | { 26 | return devConfigFile; 27 | } 28 | } 29 | catch (Exception) 30 | { 31 | // no op 32 | } 33 | 34 | return null; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebService/Runtime/Uptime.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime 6 | { 7 | /// Helper capturing runtime information. 8 | public static class Uptime 9 | { 10 | /// When the service started 11 | public static DateTimeOffset Start { get; } = DateTimeOffset.UtcNow; 12 | 13 | /// How long the service has been running 14 | public static TimeSpan Duration => DateTimeOffset.UtcNow.Subtract(Start); 15 | 16 | /// A randomly generated ID used to identify the process in the logs 17 | public static string ProcessId { get; } = "WebService." + Guid.NewGuid(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebService/v1/Controllers/DeviceModelPropertiesController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services; 6 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Filters; 7 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models; 8 | 9 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers 10 | { 11 | [Route(Version.PATH + "/[controller]"), TypeFilter(typeof(ExceptionsFilterAttribute))] 12 | public class DeviceModelPropertiesController : Controller 13 | { 14 | private readonly IDeviceModels deviceModelsService; 15 | 16 | public DeviceModelPropertiesController(IDeviceModels deviceModelsService) 17 | { 18 | this.deviceModelsService = deviceModelsService; 19 | } 20 | 21 | [HttpGet] 22 | public async Task GetAsync() 23 | { 24 | return new DeviceModelPropertyListApiModel(await this.deviceModelsService.GetPropertyNamesAsync()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebService/v1/Controllers/README.md: -------------------------------------------------------------------------------- 1 | Web service controllers 2 | ======================= 3 | 4 | ## Guidelines 5 | 6 | * Always access external services through logic in the service layer. 7 | * Consume Azure IoT SDK code through the service layer, i.e. do not reference 8 | classes from the Azure IoT SDK (or other SDKs). 9 | 10 | ## Conventions 11 | 12 | * Version controllers and models together (e.v. under 'v1' namespace). 13 | -------------------------------------------------------------------------------- /WebService/v1/Exceptions/BadRequestException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions 7 | { 8 | public class BadRequestException : CustomException 9 | { 10 | /// 11 | /// This exception is thrown by a controller when the input validation 12 | /// fails. The client should fix the request before retrying. 13 | /// 14 | public BadRequestException() : base() 15 | { 16 | } 17 | 18 | public BadRequestException(string message) : base(message) 19 | { 20 | } 21 | 22 | public BadRequestException(string message, Exception innerException) : base(message, innerException) 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebService/v1/Exceptions/InvalidDateFormatException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions 7 | { 8 | public class InvalidDateFormatException : CustomException 9 | { 10 | /// 11 | /// This exception is thrown by a controller when a datetime input validation 12 | /// fails. The client should fix the request before retrying. 13 | /// 14 | public InvalidDateFormatException() : base() 15 | { 16 | } 17 | 18 | public InvalidDateFormatException(string message) : base(message) 19 | { 20 | } 21 | 22 | public InvalidDateFormatException(string message, Exception innerException) : base(message, innerException) 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebService/v1/Exceptions/InvalidIntervalException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions 7 | { 8 | public class InvalidIntervalException : CustomException 9 | { 10 | /// 11 | /// This exception is thrown by a controller when an interval input validation 12 | /// fails. The client should fix the request before retrying. 13 | /// 14 | public InvalidIntervalException() : base() 15 | { 16 | } 17 | 18 | public InvalidIntervalException(string message) : base(message) 19 | { 20 | } 21 | 22 | public InvalidIntervalException(string message, Exception innerException) : base(message, innerException) 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebService/v1/Exceptions/InvalidIotHubConnectionStringFormatException.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions 7 | { 8 | public class InvalidIotHubConnectionStringFormatException : CustomException 9 | { 10 | /// 11 | /// This exception is thrown when the IoTHub connection string provided 12 | /// is not properly formatted. The correct format is: 13 | /// HostName=[hubname];SharedAccessKeyName=[iothubowner or service];SharedAccessKey=[null or valid key] 14 | /// 15 | public InvalidIotHubConnectionStringFormatException() : base() 16 | { 17 | } 18 | 19 | public InvalidIotHubConnectionStringFormatException(string message) : base(message) 20 | { 21 | } 22 | 23 | public InvalidIotHubConnectionStringFormatException(string message, Exception innerException) : base(message, innerException) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebService/v1/Models/DeviceModelListApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 6 | using Newtonsoft.Json; 7 | 8 | // TODO: tests 9 | // TODO: handle errors 10 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models 11 | { 12 | public class DeviceModelListApiModel 13 | { 14 | [JsonProperty(PropertyName = "Items")] 15 | public List Items { get; set; } 16 | 17 | [JsonProperty(PropertyName = "$metadata")] 18 | public Dictionary Metadata => new Dictionary 19 | { 20 | { "$type", "DeviceModelList;" + Version.NUMBER }, 21 | { "$uri", "/" + Version.PATH + "/devicemodels" } 22 | }; 23 | 24 | public DeviceModelListApiModel() 25 | { 26 | this.Items = new List(); 27 | } 28 | 29 | // Map service model to API model 30 | public static DeviceModelListApiModel FromServiceModel(IEnumerable value) 31 | { 32 | if (value == null) return null; 33 | 34 | return new DeviceModelListApiModel 35 | { 36 | Items = value.Select(DeviceModelApiModel.DeviceModelApiModel.FromServiceModel).Where(x => x != null).ToList() 37 | }; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /WebService/v1/Models/DeviceModelPropertyListApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models 7 | { 8 | public class DeviceModelPropertyListApiModel 9 | { 10 | [JsonProperty(PropertyName = "Items")] 11 | public List Items { get; set; } 12 | 13 | [JsonProperty(PropertyName = "$metadata")] 14 | public Dictionary Metadata => new Dictionary 15 | { 16 | { "$type", "DeviceModelPropertyList;" + Version.NUMBER }, 17 | { "$uri", "/" + Version.PATH + "/deviceModelProperties" } 18 | }; 19 | 20 | public DeviceModelPropertyListApiModel() 21 | { 22 | this.Items = new List(); 23 | } 24 | 25 | /// Map a service model to the corresponding API model 26 | public DeviceModelPropertyListApiModel(List values) 27 | { 28 | this.Items = values; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /WebService/v1/Models/DeviceModelScriptListModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 6 | using Newtonsoft.Json; 7 | 8 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models 9 | { 10 | public class DeviceModelScriptListModel 11 | { 12 | [JsonProperty(PropertyName = "Items")] 13 | public List Items { get; set; } 14 | 15 | [JsonProperty(PropertyName = "$metadata")] 16 | public Dictionary Metadata => new Dictionary 17 | { 18 | { "$type", "DeviceModelScriptList;" + Version.NUMBER }, 19 | { "$uri", "/" + Version.PATH + "/devicemodelscripts" } 20 | }; 21 | 22 | public DeviceModelScriptListModel() 23 | { 24 | this.Items = new List(); 25 | } 26 | 27 | // Map service model to API model 28 | public static DeviceModelScriptListModel FromServiceModel(IEnumerable value) 29 | { 30 | if (value == null) return null; 31 | 32 | return new DeviceModelScriptListModel 33 | { 34 | Items = value.Select(DeviceModelScriptApiModel.DeviceModelScriptApiModel.FromServiceModel) 35 | .Where(x => x != null) 36 | .ToList() 37 | }; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /WebService/v1/Models/Devices/BatchDeleteActionApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Devices 7 | { 8 | public class BatchDeleteActionApiModel 9 | { 10 | [JsonProperty(PropertyName = "DeviceIds")] 11 | public List DeviceIds { get; set; } 12 | 13 | public BatchDeleteActionApiModel() 14 | { 15 | this.DeviceIds = new List(); 16 | } 17 | 18 | public BatchDeleteActionApiModel(List items) 19 | { 20 | this.DeviceIds = items ?? new List(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /WebService/v1/Models/Devices/CreateActionApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions; 5 | using Newtonsoft.Json; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Devices 8 | { 9 | public class CreateActionApiModel 10 | { 11 | [JsonProperty(PropertyName = "DeviceId")] 12 | public string DeviceId { get; set; } 13 | 14 | [JsonProperty(PropertyName = "ModelId")] 15 | public string ModelId { get; set; } 16 | 17 | public CreateActionApiModel() 18 | { 19 | this.DeviceId = null; 20 | this.ModelId = null; 21 | } 22 | 23 | public void ValidateInputRequest(ILogger log) 24 | { 25 | const string INVALID_DEVICE_NAME = "Device name is invalid"; 26 | const string INVALID_DEVICE_MODEL_ID = "Device model id is invalid"; 27 | 28 | if (string.IsNullOrEmpty(this.DeviceId)) 29 | { 30 | log.Error(INVALID_DEVICE_NAME, () => new { device = this }); 31 | throw new BadRequestException(INVALID_DEVICE_NAME); 32 | } 33 | 34 | if (string.IsNullOrEmpty(this.ModelId)) 35 | { 36 | log.Error(INVALID_DEVICE_MODEL_ID, () => new { device = this }); 37 | throw new BadRequestException(INVALID_DEVICE_MODEL_ID); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /WebService/v1/Models/Helpers/DateHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Xml; 5 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Helpers 8 | { 9 | public static class DateHelper 10 | { 11 | public static DateTimeOffset? ParseDateExpression(string text, DateTimeOffset now) 12 | { 13 | if (string.IsNullOrEmpty(text)) return null; 14 | 15 | text = text.Trim(); 16 | string utext = text.ToUpper(); 17 | 18 | if (utext.Equals("NOW")) 19 | { 20 | return now; 21 | } 22 | 23 | try 24 | { 25 | if (utext.StartsWith("NOW-")) 26 | { 27 | TimeSpan delta = XmlConvert.ToTimeSpan(utext.Substring(4)); 28 | return now.Subtract(delta); 29 | } 30 | 31 | // Support the special case of "+" being url decoded to " " in case 32 | // the client forgot to encode the plus correctly using "%2b" 33 | if (utext.StartsWith("NOW+") || utext.StartsWith("NOW ")) 34 | { 35 | TimeSpan delta = XmlConvert.ToTimeSpan(utext.Substring(4)); 36 | return now.Add(delta); 37 | } 38 | 39 | return ParseDate(text); 40 | } 41 | catch (Exception e) 42 | { 43 | // log happens upstream 44 | throw new InvalidDateFormatException("Unable to parse date", e); 45 | } 46 | } 47 | 48 | public static DateTimeOffset? ParseDate(string text) 49 | { 50 | if (string.IsNullOrEmpty(text)) return null; 51 | 52 | try 53 | { 54 | return DateTimeOffset.Parse(text.Trim()); 55 | } 56 | catch (Exception e) 57 | { 58 | // log happens upstream 59 | throw new InvalidDateFormatException("Unable to parse date", e); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /WebService/v1/Models/Helpers/IntervalHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions; 4 | using System; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Helpers 7 | { 8 | public static class IntervalHelper 9 | { 10 | public static void ValidateInterval(string interval) 11 | { 12 | const string NOT_VALID = "Invalid interval"; 13 | const string EMPTY_INTERVAL = "Empty interval"; 14 | bool hasError = false; 15 | 16 | if (string.IsNullOrEmpty(interval)) 17 | { 18 | // Log happens upstream 19 | throw new InvalidIntervalException(EMPTY_INTERVAL); 20 | } 21 | 22 | try 23 | { 24 | TimeSpan t = TimeSpan.Parse(interval); 25 | 26 | if (t == TimeSpan.Zero) 27 | { 28 | hasError = true; 29 | } 30 | } 31 | catch (Exception) 32 | { 33 | hasError = true; 34 | } 35 | 36 | if (hasError) 37 | { 38 | // Log happens upstream 39 | throw new InvalidIntervalException(NOT_VALID); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /WebService/v1/Models/Helpers/ValidationApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Newtonsoft.Json; 5 | 6 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Helpers 7 | { 8 | public class ValidationApiModel 9 | { 10 | [JsonProperty(PropertyName = "IsValid")] 11 | public bool IsValid { get; set; } 12 | 13 | [JsonProperty(PropertyName = "Messages", NullValueHandling = NullValueHandling.Ignore)] 14 | public List Messages { get; set; } 15 | 16 | public ValidationApiModel() 17 | { 18 | this.IsValid = true; 19 | this.Messages = null; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /WebService/v1/Models/IoTHubApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Newtonsoft.Json; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models 6 | { 7 | // TODO: delete this model when the application supports multiple connection strings, 8 | // see SimulationsController and Devices.InitAsync 9 | public class IoTHubApiModel 10 | { 11 | [JsonProperty(PropertyName = "ConnectionString")] 12 | public string ConnectionString { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /WebService/v1/Models/README.md: -------------------------------------------------------------------------------- 1 | Web service models 2 | ================== 3 | 4 | ## Guidelines 5 | 6 | * Do not reference classes from Azure IoT SDK (or other SDKs), but reference them 7 | in the service layer preferably. 8 | * Maintain the API contract with the corresponding Java project. 9 | * Ensure datetime values are transfered using UTC timezone. 10 | 11 | ## Conventions 12 | 13 | * Add the "ApiModel" suffix to the models in this folder. This allows to 14 | distinguish these classes from the classes in the SDK and in the 15 | service layer. 16 | * Hard code JSON property names using an explicit JsonProperty attribute 17 | to avoid breaking the API contract in case of refactoring. 18 | * Use CamelCase for the API property names. 19 | * For DateTime fields use System.DateTimeOffset. 20 | * Format DateTime fields to UTC with format "yyyy-MM-dd'T'HH:mm:sszzz". 21 | -------------------------------------------------------------------------------- /WebService/v1/Models/SimulationApiModel/MetricsApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.AzureManagementAdapter; 5 | using Newtonsoft.Json; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.SimulationApiModel 8 | { 9 | public class MetricsRequestApiModel 10 | { 11 | [JsonProperty(PropertyName = "httpMethod")] 12 | public string HttpMethod { get; set; } 13 | 14 | [JsonProperty(PropertyName = "relativeUrl")] 15 | public string RelativeUrl { get; set; } 16 | 17 | public MetricsRequestApiModel() 18 | { 19 | this.HttpMethod = string.Empty; 20 | this.RelativeUrl = string.Empty; 21 | } 22 | 23 | public MetricsRequestModel ToServiceModel() 24 | { 25 | return new MetricsRequestModel() 26 | { 27 | HttpMethod = this.HttpMethod, 28 | RelativeUrl = this.RelativeUrl 29 | }; 30 | } 31 | } 32 | 33 | public class MetricsRequestsApiModel 34 | { 35 | [JsonProperty(PropertyName = "requests")] 36 | public List Requests { get; set; } 37 | 38 | public MetricsRequestsApiModel() 39 | { 40 | this.Requests = new List(); 41 | } 42 | 43 | public MetricsRequestListModel ToServiceModel() 44 | { 45 | var result = new MetricsRequestListModel(); 46 | 47 | foreach(var request in this.Requests) 48 | { 49 | result.Requests.Add(request.ToServiceModel()); 50 | } 51 | 52 | return result; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /WebService/v1/Models/SimulationApiModel/SimulationIotHub.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime; 5 | using Newtonsoft.Json; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.SimulationApiModel 8 | { 9 | public class SimulationIotHub 10 | { 11 | // This is the value used by the user interface 12 | public const string USE_DEFAULT_IOTHUB = "default"; 13 | 14 | [JsonProperty(PropertyName = "ConnectionString")] 15 | public string ConnectionString { get; set; } 16 | 17 | [JsonProperty(PropertyName = "PreprovisionedIoTHubInUse")] 18 | public bool PreprovisionedIoTHubInUse { get; set; } 19 | 20 | [JsonProperty(PropertyName = "PreprovisionedIoTHubMetricsUrl")] 21 | public string PreprovisionedIoTHubMetricsUrl { get; set; } 22 | 23 | // Default constructor used by web service requests 24 | public SimulationIotHub() 25 | { 26 | this.ConnectionString = USE_DEFAULT_IOTHUB; 27 | this.PreprovisionedIoTHubInUse = false; 28 | this.PreprovisionedIoTHubMetricsUrl = null; 29 | } 30 | 31 | public SimulationIotHub( 32 | string connectionString, 33 | bool preprovisionedIoTHubInUse = false, 34 | string preprovisionedIoTHubMetricsUrl = null) : this() 35 | { 36 | this.ConnectionString = connectionString; 37 | this.PreprovisionedIoTHubInUse = preprovisionedIoTHubInUse; 38 | this.PreprovisionedIoTHubMetricsUrl = preprovisionedIoTHubMetricsUrl; 39 | } 40 | 41 | // Map API model to service model 42 | public static string ToServiceModel(SimulationIotHub iotHub) 43 | { 44 | return iotHub != null && !IsDefaultHub(iotHub.ConnectionString) 45 | ? iotHub.ConnectionString 46 | : ServicesConfig.USE_DEFAULT_IOTHUB; 47 | } 48 | 49 | private static bool IsDefaultHub(string connectionString) 50 | { 51 | return 52 | string.IsNullOrEmpty(connectionString) || 53 | string.Equals( 54 | connectionString.Trim(), 55 | USE_DEFAULT_IOTHUB, 56 | StringComparison.OrdinalIgnoreCase); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /WebService/v1/Models/SimulationListApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models; 5 | using Newtonsoft.Json; 6 | 7 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models 8 | { 9 | public class SimulationListApiModel 10 | { 11 | [JsonProperty(PropertyName = "Items")] 12 | public List Items { get; set; } 13 | 14 | [JsonProperty(PropertyName = "$metadata")] 15 | public Dictionary Metadata => new Dictionary 16 | { 17 | { "$type", "SimulationList;" + Version.NUMBER }, 18 | { "$uri", "/" + Version.PATH + "/simulations" } 19 | }; 20 | 21 | public SimulationListApiModel() 22 | { 23 | this.Items = new List(); 24 | } 25 | 26 | /// Map a service model to the corresponding API model 27 | public SimulationListApiModel(IEnumerable simulations) 28 | { 29 | this.Items = new List(); 30 | foreach (var x in simulations) 31 | { 32 | this.Items.Add( 33 | SimulationApiModel.SimulationApiModel.FromServiceModel(x)); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /WebService/v1/Models/SimulationPatchApiModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Newtonsoft.Json; 4 | 5 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models 6 | { 7 | public class SimulationPatchApiModel 8 | { 9 | [JsonProperty(PropertyName = "ETag")] 10 | public string ETag { get; set; } 11 | 12 | [JsonProperty(PropertyName = "Enabled")] 13 | public bool? Enabled { get; set; } 14 | 15 | [JsonProperty(PropertyName = "DeleteDevicesOnce")] 16 | public bool? DeleteDevicesOnce { get; set; } 17 | 18 | public SimulationPatchApiModel() 19 | { 20 | this.Enabled = null; 21 | this.DeleteDevicesOnce = false; 22 | } 23 | 24 | /// Map an API model to the corresponding service model 25 | public Services.Models.SimulationPatch ToServiceModel(string id) 26 | { 27 | return new Services.Models.SimulationPatch 28 | { 29 | ETag = this.ETag, 30 | Id = id, 31 | Enabled = this.Enabled, 32 | DeleteDevicesOnce = this.DeleteDevicesOnce 33 | }; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /WebService/v1/Version.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1 4 | { 5 | /// Web service API version 1 information 6 | public static class Version 7 | { 8 | /// Number used for routing HTTP requests 9 | public const string NUMBER = "1"; 10 | 11 | /// Full path used in the URL 12 | public const string PATH = "v" + NUMBER; 13 | 14 | /// Date when the API version has been published 15 | public const string DATE = "201709"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/API_SPECS_DEVICE_MODEL_PROPERTIES.md: -------------------------------------------------------------------------------- 1 | API specifications - Device Model Properties 2 | ====================================== 3 | 4 | ## Get a list of device model properties 5 | 6 | The list of device model properties contains properties from all device models. 7 | 8 | Request: 9 | ``` 10 | GET /v1/deviceModelProperties 11 | ``` 12 | 13 | Response: 14 | ``` 15 | 200 OK 16 | Content-Type: application/json 17 | ``` 18 | ```json 19 | { 20 | "Items": [ 21 | "Properties.Reported.Type", 22 | "Properties.Reported.Firmware", 23 | "Properties.Reported.Model", 24 | "Properties.Reported.Location", 25 | "Properties.Reported.Latitude", 26 | "Properties.Reported.Longitude", 27 | "Properties.Reported.FirmwareUpdateStatus" 28 | ], 29 | "$metadata": { 30 | "$type": "DeviceModelPropertyList;1", 31 | "$uri": "/v1/deviceModelProperties" 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/API_SPECS_SERVICE.md: -------------------------------------------------------------------------------- 1 | API specifications - Service 2 | ============================ 3 | 4 | ## Service status 5 | 6 | Request: 7 | ``` 8 | GET /v1/status 9 | ``` 10 | 11 | Response: 12 | ``` 13 | 200 OK 14 | Content-Type: application/JSON 15 | ``` 16 | ```json 17 | { 18 | "Name": "DeviceSimulation", 19 | "Status": "OK:Alive and well|ERROR:...msg...", 20 | "CurrentTime": "2017-08-22T00:27:30+00:00", 21 | "StartTime": "2017-08-22T00:26:32+00:00", 22 | "UpTime": "88", 23 | "UID": "779e748b-fc97-4eb4-adcd-2fbe51df619c", 24 | "Properties": { 25 | "SimulationRunning": "true|false|unknown", 26 | "PreprovisionedIoTHub": "true|false" 27 | }, 28 | "Dependencies": { 29 | "PreprovisionedIoTHub": "OK|ERROR:...msg...", 30 | "Storage": "OK:...msg...|ERROR:...msg..." 31 | }, 32 | "$metadata": { 33 | "$type": "Status;v1", 34 | "$uri": "/v1/status" 35 | } 36 | } 37 | ``` 38 | 39 | * Status: the message is optional 40 | * UpTime: value specific to the process running, i.e. in case of multiple 41 | deployments each instance has a different uptime 42 | * UID: a unique value in each instance, used mostly for logging correlation 43 | * Properties: contains runtime information, e.g. whether the simulation 44 | is running and if the configuration contains a hub connection string 45 | * Dependencies: health status of the internal dependencies, e.g. hub 46 | and storage 47 | -------------------------------------------------------------------------------- /docs/BREAKING_CHANGES.md: -------------------------------------------------------------------------------- 1 | # November 2017 breaking changes 2 | 3 | 1. Fix casing of "Etag", renamed to "ETag" 4 | 2. Device models support multiple scripts, Device Models schema changed, "Script" => "Scripts" 5 | 3. Moved Simulation.Script.Interval to Simulation.Interval (all scripts share the same interval) 6 | 4. status endpoint "Simulation: on|off" => "SimulationRunning: true|false" -------------------------------------------------------------------------------- /docs/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # For more info see https://help.github.com/articles/about-codeowners 2 | 3 | # Order is important; the last matching pattern takes the most precedence. 4 | 5 | LICENSE @miwolfms @tommo-ms @saixiaohui 6 | 7 | .gitattributes @miwolfms @tommo-ms @saixiaohui 8 | .travis.yml @miwolfms @tommo-ms @saixiaohui 9 | 10 | scripts/ @miwolfms @tommo-ms @saixiaohui 11 | WebService/Auth/ @miwolfms @tommo-ms @saixiaohui 12 | Services/Runtime/ @miwolfms @tommo-ms @saixiaohui -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Microsoft Open Source Code of Conduct 2 | ===================================== 3 | 4 | This project has adopted the 5 | [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct). 6 | 7 | For more information see the 8 | [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq) 9 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with 10 | any additional questions or comments. 11 | -------------------------------------------------------------------------------- /docs/INDEX.md: -------------------------------------------------------------------------------- 1 | Azure IoT Device Simulation 2 | =========================== 3 | 4 | This service allows management of a pool of simulated devices. The primarily 5 | objective is to allow testing the end-to-end flow of device-to-cloud (D2C) 6 | telemetry and invoking cloud-to-device (C2D) methods. 7 | 8 | The microservice provides a RESTful endpoint to create a simulation (only one), 9 | start, and stop a simulation containing multiple devices. Each simulation is 10 | composed of a set of virtual devices of different types. The devices are 11 | registered with an IoTHub, send telemetry, update the twin and receive method 12 | calls. 13 | 14 | ### Features: 15 | 1. Get list of device models that can be simulated 16 | 2. Create a simulation for a set of customized device models 17 | 3. Create a "seed" or "default" simulation with 1 devices per model 18 | 4. Only one simulation per deployment can be created 19 | 5. Simulations start immediately, unless specified differently 20 | 6. Get details of the running simulation 21 | 7. Stop existing simulation 22 | 8. Start existing simulation 23 | 9. Invoke methods on the devices 24 | 10. Stateful devices, i.e. devices can simulate long-running flows, state 25 | machines, etc. 26 | 27 | ### Components 28 | 1. Web service: API for the UI to retrieve information and start/stop 29 | 2. Storage: simulation details, and status of the simulation On/Off 30 | 3. Simulation actors: background processes sending events and listening for 31 | method calls 32 | 33 | ### Dependencies 34 | 1. IoT Hub Manager, used to manage devices 35 | a. Requires an Azure IotHub. 36 | 2. Storage Adapter, used to store the simulation status 37 | 38 | # Resources 39 | 40 | * Web Service API specifications 41 | * [Device models](API_SPECS_DEVICE_MODELS.md) 42 | * [Simulations](API_SPECS_SIMULATIONS.md) 43 | * [Device model scripts](API_SPECS_DEVICE_MODEL_SCRIPTS.md) 44 | * [Service](API_SPECS_SERVICE.md) 45 | * [Device models](DEVICE_MODELS.md) 46 | * [Contributing to the project](CONTRIBUTING.md) 47 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Type of issue 2 | 3 | - [ ] Bug 4 | - [ ] New feature 5 | - [ ] Enhancement 6 | 7 | # Description 8 | 9 | ... 10 | 11 | # Steps to reproduce 12 | 13 | 1. [First step] 14 | 2. [Second step] 15 | 3. [and so on...] 16 | 17 | # Expected behavior 18 | ... 19 | 20 | # Current behavior 24 | ... 25 | 26 | # Possible solution 27 | ... 28 | 29 | # Context and Environment 30 | * Operating System: ... 31 | * GitHub branch: ... 32 | * .NET Runtime: ... 33 | 34 | # Code to reproduce the bug 35 | ``` 36 | using Xunit; 37 | 38 | public class MyTest 39 | { 40 | [Fact] 41 | public void TestToReproduceIssue() 42 | { 43 | // Arrange 44 | ... 45 | 46 | // Act 47 | ... 48 | 49 | // Assert 50 | ... 51 | } 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description and Motivation 2 | 3 | ... 4 | 5 | # Change type 6 | 7 | - [ ] Bug fix 8 | - [ ] New feature 9 | - [ ] Enhancement 10 | - [ ] Breaking change (breaks backward compatibility) 11 | 12 | **Checklist:** 13 | 14 | - [ ] All tests passed 15 | - [ ] The code follows the code style and conventions of this project 16 | - [ ] The change requires a change to the documentation 17 | - [ ] I have updated the documentation accordingly 18 | -------------------------------------------------------------------------------- /docs/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/device-simulation-dotnet/2ab9cb538a701012ff735e19d3c2606fd874c161/docs/overview.png -------------------------------------------------------------------------------- /docs/postman/Azure IoT Device Simulation solution accelerator.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "42d4611f-a74d-4241-a777-c382871d665d", 3 | "name": "Azure IoT Device Simulation solution accelerator", 4 | "values": [ 5 | { 6 | "description": { 7 | "content": "", 8 | "type": "text/plain" 9 | }, 10 | "value": "", 11 | "key": "ETag", 12 | "enabled": true 13 | } 14 | ], 15 | "_postman_variable_scope": "environment", 16 | "_postman_exported_at": "2018-08-08T13:59:32.743Z", 17 | "_postman_exported_using": "Postman/6.2.3" 18 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "3.1.302" 4 | } 5 | } -------------------------------------------------------------------------------- /scripts/.functions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) Microsoft. All rights reserved. 3 | 4 | COL_NO="\033[0m" # no color 5 | COL_ERR="\033[1;31m" # light red 6 | COL_H1="\033[1;33m" # yellow 7 | COL_H2="\033[1;36m" # light cyan 8 | 9 | header() { 10 | echo -e "${COL_H1}\n### $1 ${COL_NO}" 11 | } 12 | 13 | error() { 14 | echo -e "${COL_ERR}$1 ${COL_NO}" 15 | } 16 | 17 | check_dependency_dotnet() { 18 | set +e 19 | TEST=$(which dotnet) 20 | if [[ -z "$TEST" ]]; then 21 | echo "ERROR: 'dotnet' command not found." 22 | echo "Install .NET Core 2 and make sure the 'dotnet' command is in the PATH." 23 | echo ".NET Core 2 installation: https://dotnet.github.io" 24 | exit 1 25 | fi 26 | set -e 27 | } 28 | 29 | check_dependency_docker() { 30 | set +e 31 | TEST=$(which docker) 32 | if [[ -z "$TEST" ]]; then 33 | echo "ERROR: 'docker' command not found." 34 | echo "Install Docker and make sure the 'docker' command is in the PATH." 35 | echo "Docker installation: https://www.docker.com/community-edition#/download" 36 | exit 1 37 | fi 38 | set -e 39 | } 40 | 41 | check_dependency_git() { 42 | if ! which git >/dev/null 2>&1 ; then 43 | echo "ERROR: 'git' command not found." 44 | echo "Install git and make sure the 'git' command is in the PATH." 45 | echo "Git installation: https://git-scm.com" 46 | exit 1 47 | fi 48 | } 49 | -------------------------------------------------------------------------------- /scripts/clean-up: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) Microsoft. All rights reserved. 3 | # Note: Windows Bash doesn't support shebang extra params 4 | set -e 5 | 6 | APP_HOME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )/" 7 | source "$APP_HOME/scripts/.functions.sh" 8 | 9 | if [[ ${#APP_HOME} -lt 20 ]]; then 10 | error "Unable to detect current folder. Aborting." 11 | exit 1 12 | fi 13 | 14 | cleanup_tmp_files() { 15 | check_dependency_dotnet 16 | 17 | cd $APP_HOME 18 | header "Removing temporary folders and files..." 19 | 20 | rm -fR packages 21 | rm -fR target 22 | rm -fR out 23 | 24 | PROJECTS=$(dotnet sln list | grep 'csproj$') 25 | for PROJ in $PROJECTS; do 26 | PROJ=$(dirname "$PROJ") 27 | cd $PROJ 28 | rm -fR bin/ 29 | rm -fR obj/ 30 | cd $APP_HOME 31 | done 32 | 33 | echo -e "\nDone" 34 | } 35 | 36 | cleanup_tmp_files 37 | 38 | set +e 39 | -------------------------------------------------------------------------------- /scripts/clean-up.cmd: -------------------------------------------------------------------------------- 1 | :: Copyright (c) Microsoft. All rights reserved. 2 | 3 | @ECHO off & setlocal enableextensions enabledelayedexpansion 4 | 5 | :: strlen("\scripts\") => 9 6 | SET APP_HOME=%~dp0 7 | SET APP_HOME=%APP_HOME:~0,-9% 8 | if "%APP_HOME:~20%" == "" ( 9 | echo Unable to detect current folder. Aborting. 10 | GOTO FAIL 11 | ) 12 | 13 | :: Clean up folders containing temporary files 14 | echo Removing temporary folders and files... 15 | cd %APP_HOME% 16 | IF %ERRORLEVEL% NEQ 0 GOTO FAIL 17 | 18 | rmdir /s /q .\packages 19 | rmdir /s /q .\target 20 | rmdir /s /q .\out 21 | 22 | rmdir /s /q .\Services\bin 23 | rmdir /s /q .\Services\obj 24 | rmdir /s /q .\Services.Test\bin 25 | rmdir /s /q .\Services.Test\obj 26 | 27 | rmdir /s /q .\WebService\bin 28 | rmdir /s /q .\WebService\obj 29 | rmdir /s /q .\WebService.Test\bin 30 | rmdir /s /q .\WebService.Test\obj 31 | 32 | rmdir /s /q .\SimulationAgent\bin 33 | rmdir /s /q .\SimulationAgent\obj 34 | rmdir /s /q .\SimulationAgent.Test\bin 35 | rmdir /s /q .\SimulationAgent.Test\obj 36 | 37 | echo Done. 38 | 39 | :: - - - - - - - - - - - - - - 40 | goto :END 41 | 42 | :FAIL 43 | echo Command failed 44 | endlocal 45 | exit /B 1 46 | 47 | :END 48 | endlocal 49 | -------------------------------------------------------------------------------- /scripts/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright (c) Microsoft. All rights reserved. 3 | # Note: Windows Bash doesn't support shebang extra params 4 | set -e 5 | 6 | # Usage: 7 | # Compile the project in the local environment: ./scripts/compile 8 | # Compile the project inside a Docker container: ./scripts/compile -s 9 | # Compile the project inside a Docker container: ./scripts/compile --in-sandbox 10 | 11 | APP_HOME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )/" 12 | source "$APP_HOME/scripts/.functions.sh" 13 | 14 | # Folder where PCS sandboxes cache data. Reuse the same folder to speed up the 15 | # sandbox and to save disk space. 16 | # Use PCS_CACHE="$APP_HOME/.cache" to cache inside the project folder 17 | PCS_CACHE="/tmp/azure/iotpcs/.cache" 18 | 19 | compile() { 20 | check_dependency_dotnet 21 | 22 | cd $APP_HOME 23 | dotnet restore 24 | dotnet build --configuration Debug 25 | dotnet build --configuration Release 26 | } 27 | 28 | setup_sandbox_cache() { 29 | mkdir -p $PCS_CACHE/sandbox/.config 30 | mkdir -p $PCS_CACHE/sandbox/.dotnet 31 | mkdir -p $PCS_CACHE/sandbox/.nuget 32 | echo "Note: caching build files in $PCS_CACHE" 33 | } 34 | 35 | compile_in_sandbox() { 36 | 37 | setup_sandbox_cache 38 | 39 | cd $APP_HOME 40 | 41 | # In Windows this script should use docker.exe, in which case 42 | # the parameters syntax is different, e.g. volumes path 43 | # (i.e. C:\path\path\... vs /c/path/path/...). 44 | set +e 45 | IS_WINDOWS=$(which cmd.exe) 46 | set -e 47 | if [[ -z "$IS_WINDOWS" ]]; then 48 | check_dependency_docker 49 | docker run -it \ 50 | -v "$PCS_CACHE/sandbox/.config:/root/.config" \ 51 | -v "$PCS_CACHE/sandbox/.dotnet:/root/.dotnet" \ 52 | -v "$PCS_CACHE/sandbox/.nuget:/root/.nuget" \ 53 | -v "$APP_HOME:/opt/code" \ 54 | azureiotpcs/code-builder-dotnet:1.0-dotnetcore /opt/code/scripts/compile 55 | else 56 | # Note 'winpty' is required to provide a TTY to Docker 57 | echo "Launching cmd.exe /c winpty ..." 58 | cmd.exe /c "winpty .\scripts\compile.cmd --in-sandbox" 59 | fi 60 | } 61 | 62 | if [[ "$1" == "--in-sandbox" || "$1" == "-s" ]]; then 63 | compile_in_sandbox 64 | else 65 | compile 66 | fi 67 | 68 | set +e 69 | -------------------------------------------------------------------------------- /scripts/development/create-simulation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SIM_ID="$1" 6 | DEVICE_COUNT="$2" 7 | 8 | if [ -z "$SIM_ID" ]; then 9 | echo "Simulation ID not specified. Usage: