When upserting a simulation, keep the original/internal/readonly prop… (#261)

This commit is contained in:
Hugh Xiong 2018-09-21 14:56:13 -07:00 коммит произвёл Harleen Thind
Родитель 939333e700
Коммит 2abcd73c33
7 изменённых файлов: 96 добавлений и 77 удалений

Просмотреть файл

@ -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)