When upserting a simulation, keep the original/internal/readonly prop… (#261)
This commit is contained in:
Родитель
939333e700
Коммит
2abcd73c33
|
@ -250,7 +250,7 @@ namespace Services.Test
|
|||
// different ETag than the one we're trying to use to upsert
|
||||
var document = new Document();
|
||||
document.Id = "foo";
|
||||
document.SetPropertyValue("ETag", ETAG2);
|
||||
document.SetPropertyValue("_etag", ETAG2);
|
||||
document.SetPropertyValue("Data", JsonConvert.SerializeObject(updatedSimulation));
|
||||
var mockStorageRecord = StorageRecord.FromDocumentDb(document);
|
||||
this.mockStorageRecords.Setup(x => x.GetAsync(It.IsAny<string>())).ReturnsAsync(mockStorageRecord);
|
||||
|
@ -288,7 +288,7 @@ namespace Services.Test
|
|||
// same ETag value as the one we're trying to use to upsert with.
|
||||
var document = new Document();
|
||||
document.Id = "foo";
|
||||
document.SetPropertyValue("ETag", ETAG1);
|
||||
document.SetPropertyValue("_etag", ETAG1);
|
||||
document.SetPropertyValue("Data", JsonConvert.SerializeObject(existingSimulation));
|
||||
var mockStorageRecord = StorageRecord.FromDocumentDb(document);
|
||||
|
||||
|
@ -304,7 +304,7 @@ namespace Services.Test
|
|||
// which will contain an updated ETag
|
||||
var upsertResultDocument = new Document();
|
||||
upsertResultDocument.Id = "bar";
|
||||
upsertResultDocument.SetPropertyValue("ETag", ETAG2);
|
||||
upsertResultDocument.SetPropertyValue("_etag", ETAG2);
|
||||
upsertResultDocument.SetPropertyValue("Data", JsonConvert.SerializeObject(initialSimulation));
|
||||
var upsertResultStorageRecord = StorageRecord.FromDocumentDb(upsertResultDocument);
|
||||
|
||||
|
@ -319,47 +319,6 @@ namespace Services.Test
|
|||
Assert.Matches(ETAG2, returnedSimulationTask.Result.ETag);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void ItDoesNotAllowUsersToOverwritePartitioningStatus()
|
||||
{
|
||||
// Arrange
|
||||
var sim = new SimulationModel
|
||||
{
|
||||
Id = "1",
|
||||
Enabled = true,
|
||||
PartitioningComplete = false
|
||||
};
|
||||
var record = new ValueApiModel
|
||||
{
|
||||
Key = "1",
|
||||
Data = JsonConvert.SerializeObject(sim)
|
||||
};
|
||||
|
||||
// Create a DocumentDB Document that will be used to create a StorageRecord object
|
||||
var document = new Document();
|
||||
document.Id = "foo";
|
||||
document.SetPropertyValue("Data", JsonConvert.SerializeObject(sim));
|
||||
var storageRecord = StorageRecord.FromDocumentDb(document);
|
||||
|
||||
this.mockStorageRecords.Setup(x => x.GetAsync(It.IsAny<string>()))
|
||||
.ReturnsAsync(storageRecord);
|
||||
this.mockStorageRecords.Setup(x => x.UpsertAsync(It.IsAny<StorageRecord>()))
|
||||
.ReturnsAsync(storageRecord);
|
||||
|
||||
// Act
|
||||
var update = new SimulationModel
|
||||
{
|
||||
Id = sim.Id,
|
||||
Enabled = true,
|
||||
PartitioningComplete = true,
|
||||
ETag = "*"
|
||||
};
|
||||
SimulationModel result = this.target.UpsertAsync(update).CompleteOrTimeout().Result;
|
||||
|
||||
// Assert
|
||||
Assert.False(result.PartitioningComplete);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void ItCreatesNewSimulationsWithPartitioningStateNotComplete()
|
||||
{
|
||||
|
|
|
@ -91,17 +91,19 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
// When unspecified, a simulation is enabled
|
||||
this.Enabled = true;
|
||||
|
||||
this.StartTime = DateTimeOffset.MinValue;
|
||||
this.EndTime = DateTimeOffset.MaxValue;
|
||||
// By default, a new simulation requires partitioning
|
||||
this.PartitioningComplete = false;
|
||||
|
||||
// by default, use environment variable
|
||||
this.IotHubConnectionStrings = new List<string> { ServicesConfig.USE_DEFAULT_IOTHUB };
|
||||
|
||||
// By default, run forever
|
||||
this.StartTime = DateTimeOffset.MinValue;
|
||||
this.EndTime = DateTimeOffset.MaxValue;
|
||||
|
||||
this.DeviceModels = new List<DeviceModelRef>();
|
||||
this.PartitioningComplete = false;
|
||||
this.CustomDevices = new List<CustomDeviceRef>();
|
||||
this.Statistics = new StatisticsRef();
|
||||
|
||||
this.PartitioningComplete = false;
|
||||
}
|
||||
|
||||
public class DeviceModelRef
|
||||
|
|
|
@ -226,9 +226,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
}
|
||||
|
||||
simulation.Created = existingSimulation.Created;
|
||||
|
||||
// This value cannot be set by the user, making sure we persist the existing state
|
||||
simulation.PartitioningComplete = existingSimulation.PartitioningComplete;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage
|
|||
var result = new StorageRecord
|
||||
{
|
||||
Id = document.Id,
|
||||
ETag = document.GetPropertyValue<string>("ETag"),
|
||||
ETag = document.ETag,
|
||||
Data = document.GetPropertyValue<string>("Data")
|
||||
};
|
||||
|
||||
|
|
|
@ -58,12 +58,41 @@ namespace WebService.Test.v1.Models.SimulationApiModel
|
|||
var simulationApiModel = this.GetSimulationApiModel();
|
||||
|
||||
// Act
|
||||
var result = simulationApiModel.ToServiceModel(simulationApiModel.Id);
|
||||
var result = simulationApiModel.ToServiceModel(null, simulationApiModel.Id);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<Simulation>(result);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void ItKeepsInternalReadonlyProperties()
|
||||
{
|
||||
// Arrange
|
||||
var simulationApiModel = this.GetSimulationApiModel();
|
||||
|
||||
var existingSimulation1 = new Simulation
|
||||
{
|
||||
PartitioningComplete = true,
|
||||
StoppedTime = DateTimeOffset.UtcNow.AddHours(-10)
|
||||
};
|
||||
|
||||
var existingSimulation2 = new Simulation
|
||||
{
|
||||
PartitioningComplete = false,
|
||||
StoppedTime = DateTimeOffset.UtcNow.AddHours(-20)
|
||||
};
|
||||
|
||||
// Act
|
||||
var result1 = simulationApiModel.ToServiceModel(existingSimulation1, simulationApiModel.Id);
|
||||
var result2 = simulationApiModel.ToServiceModel(existingSimulation2, simulationApiModel.Id);
|
||||
|
||||
// Assert
|
||||
Assert.True(result1.PartitioningComplete);
|
||||
Assert.False(result2.PartitioningComplete);
|
||||
Assert.Equal(existingSimulation1.StoppedTime, result1.StoppedTime);
|
||||
Assert.Equal(existingSimulation2.StoppedTime, result2.StoppedTime);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void NoExceptionThrownForValidDeviceModelApiModel()
|
||||
{
|
||||
|
|
|
@ -5,7 +5,9 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions;
|
||||
|
@ -13,6 +15,7 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Filters;
|
|||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Devices;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.SimulationApiModel;
|
||||
using SimulationStatistics = Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.SimulationStatistics;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers
|
||||
{
|
||||
|
@ -87,7 +90,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
simulationApiModel = new SimulationApiModel();
|
||||
}
|
||||
|
||||
var simulation = await this.simulationsService.InsertAsync(simulationApiModel.ToServiceModel(), template);
|
||||
var simulation = await this.simulationsService.InsertAsync(simulationApiModel.ToServiceModel(null), template);
|
||||
return SimulationApiModel.FromServiceModel(
|
||||
simulation, this.servicesConfig, this.deploymentConfig, this.connectionStringManager, this.simulationRunner, this.rateReporter);
|
||||
}
|
||||
|
@ -105,7 +108,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
throw new BadRequestException("No data provided, request object is empty.");
|
||||
}
|
||||
|
||||
var simulation = await this.simulationsService.UpsertAsync(simulationApiModel.ToServiceModel(id));
|
||||
// Load the existing resource, so that internal properties can be copied
|
||||
var existingSimulation = await this.GetExistingSimulationAsync(id);
|
||||
|
||||
var simulation = await this.simulationsService.UpsertAsync(simulationApiModel.ToServiceModel(existingSimulation, id));
|
||||
return SimulationApiModel.FromServiceModel(
|
||||
simulation, this.servicesConfig, this.deploymentConfig, this.connectionStringManager, this.simulationRunner, this.rateReporter);
|
||||
}
|
||||
|
@ -155,11 +161,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
throw new BadRequestException("No data or invalid data provided");
|
||||
}
|
||||
|
||||
var patchServiceModel = patch.ToServiceModel(id);
|
||||
|
||||
SimulationPatch patchServiceModel = patch.ToServiceModel(id);
|
||||
if (patchServiceModel.Enabled == false)
|
||||
{
|
||||
patchServiceModel.Statistics = new Services.Models.SimulationStatistics
|
||||
patchServiceModel.Statistics = new SimulationStatistics
|
||||
{
|
||||
AverageMessagesPerSecond = this.rateReporter.GetThroughputForMessages(),
|
||||
TotalMessagesSent = this.simulationRunner.TotalMessagesCount
|
||||
|
@ -176,5 +181,17 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
{
|
||||
await this.simulationsService.DeleteAsync(id);
|
||||
}
|
||||
|
||||
private async Task<Simulation> GetExistingSimulationAsync(string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await this.simulationsService.GetAsync(id);
|
||||
}
|
||||
catch (ResourceNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Sim
|
|||
[JsonProperty(PropertyName = "Enabled")]
|
||||
public bool? Enabled { get; set; }
|
||||
|
||||
// Note: read-only property, used only to report the simulation status
|
||||
[JsonProperty(PropertyName = "Running")]
|
||||
public bool? Running { get; set; }
|
||||
|
||||
|
@ -50,15 +51,18 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Sim
|
|||
[JsonProperty(PropertyName = "EndTime")]
|
||||
public string EndTime { get; set; }
|
||||
|
||||
// Note: read-only property, used only to report the simulation status
|
||||
[JsonProperty(PropertyName = "StoppedTime")]
|
||||
public string StoppedTime { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceModels")]
|
||||
public IList<SimulationDeviceModelRef> DeviceModels { get; set; }
|
||||
|
||||
// Note: read-only property, used only to report the simulation status
|
||||
[JsonProperty(PropertyName = "Statistics")]
|
||||
public SimulationStatistics Statistics{ get; set; }
|
||||
public SimulationStatistics Statistics { get; set; }
|
||||
|
||||
// Note: read-only metadata
|
||||
[JsonProperty(PropertyName = "$metadata", Order = 1000)]
|
||||
public IDictionary<string, string> Metadata => new Dictionary<string, string>
|
||||
{
|
||||
|
@ -86,25 +90,36 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Sim
|
|||
this.Statistics = new SimulationStatistics();
|
||||
}
|
||||
|
||||
// Map API model to service model
|
||||
public Simulation ToServiceModel(string id = "")
|
||||
// Map API model to service model, keeping the original fields when needed
|
||||
public Simulation ToServiceModel(Simulation existingSimulation, string id = "")
|
||||
{
|
||||
this.Id = id;
|
||||
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var result = new Simulation
|
||||
// ID can be empty, e.g. with POST requests
|
||||
this.Id = id;
|
||||
|
||||
// Use the existing simulation fields if available, so that read-only values are not lost
|
||||
// e.g. the state of partitioning, device creation, etc.
|
||||
var result = new Simulation();
|
||||
if (existingSimulation != null)
|
||||
{
|
||||
ETag = this.ETag,
|
||||
Id = this.Id,
|
||||
Name = this.Name,
|
||||
Description = this.Description,
|
||||
// When unspecified, a simulation is enabled
|
||||
Enabled = this.Enabled ?? true,
|
||||
StartTime = DateHelper.ParseDateExpression(this.StartTime, now),
|
||||
EndTime = DateHelper.ParseDateExpression(this.EndTime, now),
|
||||
DeviceModels = this.DeviceModels?.Select(x => x.ToServiceModel()).ToList()
|
||||
};
|
||||
result = existingSimulation;
|
||||
}
|
||||
|
||||
result.ETag = this.ETag;
|
||||
result.Id = this.Id;
|
||||
result.Name = this.Name;
|
||||
result.Description = this.Description;
|
||||
result.StartTime = DateHelper.ParseDateExpression(this.StartTime, now);
|
||||
result.EndTime = DateHelper.ParseDateExpression(this.EndTime, now);
|
||||
result.DeviceModels = this.DeviceModels?.Select(x => x.ToServiceModel()).ToList();
|
||||
|
||||
// Overwrite the value only if the request included the field, i.e. don't
|
||||
// enable/disable the simulation if the user didn't explicitly ask to.
|
||||
if (this.Enabled.HasValue)
|
||||
{
|
||||
result.Enabled = this.Enabled.Value;
|
||||
}
|
||||
|
||||
foreach (var hub in this.IotHubs)
|
||||
{
|
||||
|
@ -228,8 +243,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Sim
|
|||
|
||||
// Append additional Hub properties and Statistics
|
||||
private void AppendHubPropertiesAndStatistics(
|
||||
IServicesConfig servicesConfig,
|
||||
IDeploymentConfig deploymentConfig,
|
||||
IServicesConfig servicesConfig,
|
||||
IDeploymentConfig deploymentConfig,
|
||||
IIotHubConnectionStringManager connectionStringManager,
|
||||
ISimulationRunner simulationRunner,
|
||||
IRateLimiting rateReporter)
|
||||
|
|
Загрузка…
Ссылка в новой задаче