Skip to content
This repository was archived by the owner on Oct 11, 2023. It is now read-only.

Commit a5da099

Browse files
authored
Multiple simulation support and adding stats to simulation definition (#234)
* Support for configuring multiple simulations but running only 1 at a time * Support for saving Simulation statistics
1 parent 11c4dd5 commit a5da099

File tree

19 files changed

+536
-446
lines changed

19 files changed

+536
-446
lines changed

Services.Test/SimulationsTest.cs

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -138,23 +138,6 @@ public void CreateWithInvalidTemplate()
138138
.Wait(Constants.TEST_TIMEOUT);
139139
}
140140

141-
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
142-
public void CreatingMultipleSimulationsIsNotAllowed()
143-
{
144-
// Arrange
145-
this.ThereAreSomeDeviceModels();
146-
this.ThereIsAnEnabledSimulationInTheStorage();
147-
var s = new SimulationModel { Id = Guid.NewGuid().ToString(), Enabled = false };
148-
149-
// Act + Assert
150-
// This fails because only 1 solution can be created
151-
Assert.ThrowsAsync<ConflictingResourceException>(async () => await this.target.InsertAsync(s))
152-
.Wait(Constants.TEST_TIMEOUT);
153-
// This fails because only "1" can be used as a simulation ID
154-
Assert.ThrowsAsync<InvalidInputException>(async () => await this.target.UpsertAsync(s))
155-
.Wait(Constants.TEST_TIMEOUT);
156-
}
157-
158141
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
159142
public void CreatedSimulationsAreStored()
160143
{
@@ -201,10 +184,10 @@ public void SimulationsCanBeUpserted()
201184
public void UpsertRequiresIdWhileInsertDoesNot()
202185
{
203186
// Arrange
204-
var s1 = new SimulationModel();
205-
var s2 = new SimulationModel();
187+
var s1 = new SimulationModel() { Name = "Test Simulation 1"};
188+
var s2 = new SimulationModel() { Name = "Test Simulation 2" };
206189
this.ThereAreNoSimulationsInTheStorage();
207-
190+
208191
// Act - No exception occurs
209192
this.target.InsertAsync(s1).Wait(Constants.TEST_TIMEOUT);
210193

@@ -221,7 +204,7 @@ public void UpsertUsesOptimisticConcurrency()
221204
const string ETAG2 = "002";
222205

223206
// Initial simulation
224-
var simulation1 = new SimulationModel { Id = SIMULATION_ID, ETag = ETAG1 };
207+
var simulation1 = new SimulationModel { Id = SIMULATION_ID, Name = "Test Simulation 1", ETag = ETAG1 };
225208
var storageRecord1 = new ValueApiModel
226209
{
227210
Key = SIMULATION_ID,
@@ -232,7 +215,7 @@ public void UpsertUsesOptimisticConcurrency()
232215
storageList1.Items.Add(storageRecord1);
233216

234217
// Simulation after update
235-
var simulation2 = new SimulationModel { Id = SIMULATION_ID, ETag = ETAG2 };
218+
var simulation2 = new SimulationModel { Id = SIMULATION_ID, Name = "Test Simulation 2", ETag = ETAG2 };
236219
var storageRecord2 = new ValueApiModel
237220
{
238221
Key = SIMULATION_ID,
@@ -253,9 +236,11 @@ public void UpsertUsesOptimisticConcurrency()
253236

254237
// Arrange - the ETag won't match
255238
this.storage.Setup(x => x.GetAllAsync(STORAGE_COLLECTION)).ReturnsAsync(storageList2);
239+
this.storage.Setup(x => x.GetAsync(STORAGE_COLLECTION, SIMULATION_ID)).ReturnsAsync(storageRecord2);
256240

257241
// Act + Assert
258242
var simulationOutOfDate = new SimulationModel { Id = SIMULATION_ID, ETag = ETAG1 };
243+
259244
Assert.ThrowsAsync<ResourceOutOfDateException>(
260245
async () => await this.target.UpsertAsync(simulationOutOfDate))
261246
.Wait(Constants.TEST_TIMEOUT);
@@ -269,15 +254,17 @@ public void ThereAreNoNullPropertiesInTheDeviceModel()
269254
this.ThereAreNoSimulationsInTheStorage();
270255

271256
// Arrange the simulation data returned by the storage adapter
257+
var id = SIMULATION_ID;
272258
var simulation = new SimulationModel
273259
{
274-
Id = SIMULATION_ID,
260+
Id = id,
261+
Name = "Test Simulation",
275262
ETag = "ETag0",
276263
Enabled = true
277264
};
278265
var updatedValue = new ValueApiModel
279266
{
280-
Key = SIMULATION_ID,
267+
Key = id,
281268
Data = JsonConvert.SerializeObject(simulation),
282269
ETag = simulation.ETag
283270
};
@@ -290,26 +277,11 @@ public void ThereAreNoNullPropertiesInTheDeviceModel()
290277
// Assert
291278
this.storage.Verify(x => x.UpdateAsync(
292279
STORAGE_COLLECTION,
293-
SIMULATION_ID,
294-
It.Is<string>(s => !s.Contains("null")),
280+
id,
281+
It.IsAny<string>(),
295282
"ETag0"));
296283
}
297284

298-
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
299-
public void CreatingMultipleSimulationsViaUpsertIsNotAllowed()
300-
{
301-
// Arrange
302-
this.ThereAreSomeDeviceModels();
303-
this.ThereIsAnEnabledSimulationInTheStorage();
304-
var s = new SimulationModel { Id = Guid.NewGuid().ToString(), Enabled = false };
305-
306-
// Act + Assert
307-
// Note: the exception is 'InvalidInputException' because only id='1' is allowed. This will
308-
// change in future when multiple simulation will be allowed.
309-
Assert.ThrowsAsync<InvalidInputException>(async () => await this.target.UpsertAsync(s))
310-
.Wait(Constants.TEST_TIMEOUT);
311-
}
312-
313285
private void ThereAreSomeDeviceModels()
314286
{
315287
this.deviceModels.Setup(x => x.GetListAsync())
@@ -320,7 +292,7 @@ private void ThereAreNoSimulationsInTheStorage()
320292
{
321293
this.storage.Setup(x => x.GetAllAsync(STORAGE_COLLECTION)).ReturnsAsync(new ValueListApiModel());
322294
// In case the test inserts a record, return a valid storage object
323-
this.storage.Setup(x => x.UpdateAsync(STORAGE_COLLECTION, SIMULATION_ID, It.IsAny<string>(), "*"))
295+
this.storage.Setup(x => x.UpdateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
324296
.ReturnsAsync(new ValueApiModel { Key = SIMULATION_ID, Data = "{}", ETag = "someETag" });
325297
}
326298

Services/Concurrency/RateLimiting.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,6 @@ public long GetPauseForNextMessage()
9797
/// <summary>
9898
/// Get message throughput (messages per second)
9999
/// </summary>
100-
public double GetThroughputForMessages() => this.messaging.GetThroughputForMessages();
100+
public double GetThroughputForMessages() => System.Math.Ceiling(this.messaging.GetThroughputForMessages() * 100) / 100;
101101
}
102102
}

Services/Devices.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Linq;
66
using System.Threading.Tasks;
77
using Microsoft.Azure.Devices;
8-
using Microsoft.Azure.Devices.Client;
98
using Microsoft.Azure.Devices.Common.Exceptions;
109
using Microsoft.Azure.Devices.Shared;
1110
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
@@ -54,7 +53,9 @@ public interface IDevices
5453

5554
Task DeleteListAsync(IEnumerable<string> deviceIds);
5655

57-
// Generate a device Id
56+
/// <summary>
57+
/// Generate a device Id
58+
/// </summary>
5859
string GenerateId(string deviceModelId, int position);
5960
}
6061

@@ -347,7 +348,9 @@ public async Task DeleteListAsync(IEnumerable<string> deviceIds)
347348
}
348349
}
349350

350-
// Generate a device Id
351+
/// <summary>
352+
/// Generate a device Id
353+
/// </summary>
351354
public string GenerateId(string deviceModelId, int position)
352355
{
353356
return deviceModelId + "." + position;

Services/Models/Simulation.cs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,46 @@ public class Simulation
1111
{
1212
private DateTimeOffset? startTime;
1313
private DateTimeOffset? endTime;
14-
private string iotHubConnectionString;
14+
private IList<string> iotHubConnectionStrings;
1515

16+
// When Simulation is written to storage, Id and Etag are not serialized as part of body
17+
// These are instead written in dedicated columns (key and eTag)
1618
[JsonIgnore]
1719
public string ETag { get; set; }
1820

1921
[JsonIgnore]
2022
public string Id { get; set; }
2123

24+
public string Name { get; set; }
25+
public string Description { get; set; }
2226
public bool Enabled { get; set; }
2327
public IList<DeviceModelRef> DeviceModels { get; set; }
2428
public DateTimeOffset Created { get; set; }
2529
public DateTimeOffset Modified { get; set; }
30+
public StatisticsRef Statistics { get; set; }
2631
public IList<CustomDeviceRef> CustomDevices { get; set; }
2732

33+
// StartTime is the time when Simulation was started
2834
public DateTimeOffset? StartTime
2935
{
3036
get => this.startTime;
3137
set => this.startTime = value ?? DateTimeOffset.MinValue;
3238
}
3339

40+
// EndTime is the time when Simulation ended after running for scheduled duration
3441
public DateTimeOffset? EndTime
3542
{
3643
get => this.endTime;
3744
set => this.endTime = value ?? DateTimeOffset.MaxValue;
3845
}
3946

40-
public string IotHubConnectionString
47+
// StoppedTime is the time when Simulation was explicitly stopped by user
48+
public DateTimeOffset? StoppedTime { get; set; }
49+
50+
public IList<string> IotHubConnectionStrings
4151
{
42-
get => this.iotHubConnectionString;
43-
set => this.iotHubConnectionString = value ?? ServicesConfig.USE_DEFAULT_IOTHUB;
52+
get => this.iotHubConnectionStrings;
53+
set => this.iotHubConnectionStrings = value ?? new List<string> { };
4454
}
4555

4656
public Simulation()
@@ -52,9 +62,10 @@ public Simulation()
5262
this.EndTime = DateTimeOffset.MaxValue;
5363

5464
// by default, use environment variable
55-
this.IotHubConnectionString = ServicesConfig.USE_DEFAULT_IOTHUB;
65+
this.IotHubConnectionStrings = new List<string> { };
5666
this.DeviceModels = new List<DeviceModelRef>();
5767
this.CustomDevices = new List<CustomDeviceRef>();
68+
this.Statistics = new StatisticsRef();
5869
}
5970

6071
public class DeviceModelRef
@@ -70,6 +81,12 @@ public class CustomDeviceRef
7081
public DeviceModelRef DeviceModel { get; set; }
7182
}
7283

84+
public class StatisticsRef
85+
{
86+
public long TotalMessagesSent { get; set; }
87+
public double AverageMessagesPerSecond { get; set; }
88+
}
89+
7390
public class DeviceModelOverride
7491
{
7592
// Optional field, used to customize the scripts which update the device state

Services/Models/SimulationPatch.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,12 @@ public class SimulationPatch
77
public string ETag { get; set; }
88
public string Id { get; set; }
99
public bool? Enabled { get; set; }
10+
public SimulationStatistics Statistics { get; set; }
11+
}
12+
13+
public class SimulationStatistics
14+
{
15+
public long TotalMessagesSent { get; set; }
16+
public double AverageMessagesPerSecond { get; set; }
1017
}
1118
}

0 commit comments

Comments
 (0)