* Replay file  APIs

* update settings

* PR comments

* update owners

* typo

* Update DeviceModelScriptsTest.cs
This commit is contained in:
Avani Patel 2019-01-16 10:59:21 -08:00 коммит произвёл Xiaohui Sai
Родитель e2af38194a
Коммит 942b2d5593
21 изменённых файлов: 583 добавлений и 54 удалений

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

@ -49,14 +49,14 @@ namespace Services.Test
// Arrange
var id = Guid.NewGuid().ToString();
var eTag = Guid.NewGuid().ToString();
var deviceModelScript = new DeviceModelScript { Id = id, ETag = eTag };
var deviceModelScript = new DataFile { Id = id, ETag = eTag };
this.storage
.Setup(x => x.UpdateAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(this.BuildValueApiModel(deviceModelScript));
// Act
DeviceModelScript result = this.target.InsertAsync(deviceModelScript).Result;
DataFile result = this.target.InsertAsync(deviceModelScript).Result;
// Assert
Assert.NotNull(result);
@ -73,10 +73,10 @@ namespace Services.Test
// Arrange
var id = Guid.NewGuid().ToString();
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "oldEtag" };
var deviceModelScript = new DataFile { Id = id, ETag = "oldEtag" };
this.TheScriptExists(id, deviceModelScript);
var updatedSimulationScript = new DeviceModelScript { Id = id, ETag = "newETag" };
var updatedSimulationScript = new DataFile { Id = id, ETag = "newETag" };
this.storage
.Setup(x => x.UpdateAsync(
STORAGE_COLLECTION,
@ -94,7 +94,7 @@ namespace Services.Test
this.storage.Verify(x => x.UpdateAsync(
STORAGE_COLLECTION,
id,
It.Is<string>(json => JsonConvert.DeserializeObject<DeviceModelScript>(json).Id == id && !json.Contains("ETag")),
It.Is<string>(json => JsonConvert.DeserializeObject<DataFile>(json).Id == id && !json.Contains("ETag")),
"oldEtag"), Times.Once());
Assert.Equal(updatedSimulationScript.Id, deviceModelScript.Id);
@ -107,7 +107,7 @@ namespace Services.Test
{
// Arrange
var id = Guid.NewGuid().ToString();
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "Etag" };
var deviceModelScript = new DataFile { Id = id, ETag = "Etag" };
this.TheScriptDoesntExist(id);
this.storage
.Setup(x => x.UpdateAsync(
@ -133,11 +133,11 @@ namespace Services.Test
{
// Arrange
var id = Guid.NewGuid().ToString();
var deviceModelScriptInStorage = new DeviceModelScript { Id = id, ETag = "ETag" };
var deviceModelScriptInStorage = new DataFile { Id = id, ETag = "ETag" };
this.TheScriptExists(id, deviceModelScriptInStorage);
// Act & Assert
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "not-matching-Etag" };
var deviceModelScript = new DataFile { Id = id, ETag = "not-matching-Etag" };
Assert.ThrowsAsync<ConflictingResourceException>(
async () => await this.target.UpsertAsync(deviceModelScript))
.Wait(Constants.TEST_TIMEOUT);
@ -147,7 +147,7 @@ namespace Services.Test
public void ItThrowsExceptionWhenInsertDeviceModelScriptFailed()
{
// Arrange
var deviceModelScript = new DeviceModelScript { Id = "id", ETag = "Etag" };
var deviceModelScript = new DataFile { Id = "id", ETag = "Etag" };
this.storage
.Setup(x => x.UpdateAsync(
It.IsAny<string>(),
@ -166,7 +166,7 @@ namespace Services.Test
public void ItFailsToUpsertWhenUnableToFetchScriptFromStorage()
{
// Arrange
var deviceModelScript = new DeviceModelScript { Id = "id", ETag = "Etag" };
var deviceModelScript = new DataFile { Id = "id", ETag = "Etag" };
this.storage
.Setup(x => x.GetAsync(It.IsAny<string>(), It.IsAny<string>()))
.ThrowsAsync(new SomeException());
@ -182,7 +182,7 @@ namespace Services.Test
{
// Arrange
var id = Guid.NewGuid().ToString();
var deviceModelScript = new DeviceModelScript { Id = id, ETag = "Etag" };
var deviceModelScript = new DataFile { Id = id, ETag = "Etag" };
this.TheScriptExists(id, deviceModelScript);
this.storage
@ -203,7 +203,7 @@ namespace Services.Test
public void ItThrowsExternalDependencyExceptionWhenFailedFetchingDeviceModelScriptInStorage()
{
// Arrange
var deviceModelScript = new DeviceModelScript { Id = "id", ETag = "Etag" };
var deviceModelScript = new DataFile { Id = "id", ETag = "Etag" };
// Act
var ex = Record.Exception(() => this.target.UpsertAsync(deviceModelScript).Result);
@ -268,7 +268,7 @@ namespace Services.Test
var list = new ValueListApiModel();
var value = new ValueApiModel
{
Key = "key",
Key = "key1",
Data = "{ 'invalid': json",
ETag = "etag"
};
@ -286,14 +286,14 @@ namespace Services.Test
.Throws<ResourceNotFoundException>();
}
private void TheScriptExists(string id, DeviceModelScript deviceModelScript)
private void TheScriptExists(string id, DataFile deviceModelScript)
{
this.storage
.Setup(x => x.GetAsync(STORAGE_COLLECTION, id))
.ReturnsAsync(this.BuildValueApiModel(deviceModelScript));
}
private ValueApiModel BuildValueApiModel(DeviceModelScript deviceModelScript)
private ValueApiModel BuildValueApiModel(DataFile deviceModelScript)
{
return new ValueApiModel
{

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

@ -0,0 +1,133 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage.CosmosDbSql;
using Moq;
using Newtonsoft.Json;
using Services.Test.helpers;
using System;
using Xunit;
namespace Services.Test
{
public class ReplayFileServiceTest
{
private const string REPLAY_FILES = "replayFiles";
private readonly Mock<ILogger> log;
private readonly Mock<IServicesConfig> config;
private readonly Mock<IEngines> enginesFactory;
private readonly Mock<IEngine> replayFilesStorage;
private readonly Mock<ILogger> logger;
private readonly ReplayFileService target;
public ReplayFileServiceTest()
{
this.log = new Mock<ILogger>();
this.config = new Mock<IServicesConfig>();
this.enginesFactory = new Mock<IEngines>();
this.replayFilesStorage = new Mock<IEngine>();
this.replayFilesStorage.Setup(x => x.BuildRecord(It.IsAny<string>(), It.IsAny<string>()))
.Returns((string id, string json) => new DataRecord { Id = id, Data = json });
this.replayFilesStorage.Setup(x => x.BuildRecord(It.IsAny<string>()))
.Returns((string id) => new DataRecord { Id = id });
this.config.SetupGet(x => x.ReplayFilesStorage)
.Returns(new Config { CosmosDbSqlCollection = REPLAY_FILES });
this.enginesFactory
.Setup(x => x.Build(It.Is<Config>(c => c.CosmosDbSqlCollection == REPLAY_FILES)))
.Returns(this.replayFilesStorage.Object);
this.target = new ReplayFileService(
this.config.Object,
this.enginesFactory.Object,
this.replayFilesStorage.Object,
this.log.Object);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItCreatesReplayFileInStorage()
{
// Arrange
var id = Guid.NewGuid().ToString();
var replayFile = new DataFile { Id = id, Content = "1, 2, 3", ETag = "tag" };
this.replayFilesStorage
.Setup(x => x.CreateAsync(It.IsAny<IDataRecord>()))
.ReturnsAsync(new DataRecord { Id = id, Data = JsonConvert.SerializeObject(replayFile) });
// Act
DataFile result = this.target.InsertAsync(replayFile).Result;
// Assert
Assert.NotNull(result);
Assert.Equal(replayFile.Id, result.Id);
Assert.Equal(replayFile.Content, result.Content);
this.replayFilesStorage.Verify(
x => x.CreateAsync(It.Is<IDataRecord>(n => n.GetId() == id)), Times.Once());
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItThrowsExceptionWhenCreateReplayFileFails()
{
// Arrange
DataFile replayFile = new DataFile();
replayFile.Content = "1, 2, 3, 4, 5";
this.replayFilesStorage
.Setup(x => x.CreateAsync(It.IsAny<IDataRecord>()))
.ThrowsAsync(new SomeException());
// Act & Assert
Assert.ThrowsAsync<ExternalDependencyException>(
async () => await this.target.InsertAsync(replayFile))
.Wait(Constants.TEST_TIMEOUT);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItThrowsExceptionWhenDeleteReplayFileFailes()
{
// Arrange
this.replayFilesStorage
.Setup(x => x.DeleteAsync(It.IsAny<string>()))
.ThrowsAsync(new SomeException());
// Act & Assert
Assert.ThrowsAsync<ExternalDependencyException>(
async () => await this.target.DeleteAsync("Id"))
.Wait(Constants.TEST_TIMEOUT);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItFailsToGetReplayFileWhenGetAsyncFails()
{
// Arrange
this.replayFilesStorage
.Setup(x => x.GetAsync(It.IsAny<string>()))
.ThrowsAsync(new SomeException());
// Act & Assert
Assert.ThrowsAsync<ExternalDependencyException>(
async () => await this.target.GetAsync("Id"))
.Wait(Constants.TEST_TIMEOUT);
}
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
public void ItThrowsExceptionForInvalidId()
{
// Act & Assert
Assert.ThrowsAsync<InvalidInputException>(
async () => await this.target.GetAsync(string.Empty))
.Wait(Constants.TEST_TIMEOUT);
}
}
}

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

@ -17,22 +17,22 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
/// <summary>
/// Get list of device model scripts.
/// </summary>
Task<IEnumerable<DeviceModelScript>> GetListAsync();
Task<IEnumerable<DataFile>> GetListAsync();
/// <summary>
/// Get a device model script.
/// </summary>
Task<DeviceModelScript> GetAsync(string id);
Task<DataFile> GetAsync(string id);
/// <summary>
/// Create a device model script.
/// </summary>
Task<DeviceModelScript> InsertAsync(DeviceModelScript deviceModelScript);
Task<DataFile> InsertAsync(DataFile deviceModelScript);
/// <summary>
/// Create or replace a device model script.
/// </summary>
Task<DeviceModelScript> UpsertAsync(DeviceModelScript deviceModelScript);
Task<DataFile> UpsertAsync(DataFile deviceModelScript);
/// <summary>
/// Delete a device model script.
@ -74,7 +74,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
/// <summary>
/// Get a device model script.
/// </summary>
public async Task<DeviceModelScript> GetAsync(string id)
public async Task<DataFile> GetAsync(string id)
{
if (string.IsNullOrEmpty(id))
{
@ -99,7 +99,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
try
{
var deviceModelScript = JsonConvert.DeserializeObject<DeviceModelScript>(item.Data);
var deviceModelScript = JsonConvert.DeserializeObject<DataFile>(item.Data);
deviceModelScript.ETag = item.ETag;
return deviceModelScript;
}
@ -113,7 +113,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
/// <summary>
/// Get list of device model scripts.
/// </summary>
public async Task<IEnumerable<DeviceModelScript>> GetListAsync()
public async Task<IEnumerable<DataFile>> GetListAsync()
{
ValueListApiModel data;
@ -129,13 +129,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
try
{
var results = new List<DeviceModelScript>();
var results = new List<DataFile>();
foreach (var item in data.Items)
{
var deviceModelScript = JsonConvert.DeserializeObject<DeviceModelScript>(item.Data);
var deviceModelScript = JsonConvert.DeserializeObject<DataFile>(item.Data);
deviceModelScript.ETag = item.ETag;
deviceModelScript.Type = ScriptInterpreter.JAVASCRIPT_SCRIPT;
deviceModelScript.Path = DeviceModelScript.DeviceModelScriptPath.Storage;
deviceModelScript.Path = DataFile.FilePath.Storage;
results.Add(deviceModelScript);
}
@ -151,7 +151,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
/// <summary>
/// Create a device model script.
/// </summary>
public async Task<DeviceModelScript> InsertAsync(DeviceModelScript deviceModelScript)
public async Task<DataFile> InsertAsync(DataFile deviceModelScript)
{
deviceModelScript.Created = DateTimeOffset.UtcNow;
deviceModelScript.Modified = deviceModelScript.Created;
@ -188,7 +188,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
/// <summary>
/// Create or replace a device model script.
/// </summary>
public async Task<DeviceModelScript> UpsertAsync(DeviceModelScript deviceModelScript)
public async Task<DataFile> UpsertAsync(DataFile deviceModelScript)
{
var id = deviceModelScript.Id;
var eTag = deviceModelScript.ETag;

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

@ -2,13 +2,12 @@
using System;
using System.Runtime.Serialization;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
{
public class DeviceModelScript
public class DataFile
{
[JsonIgnore]
public string ETag { get; set; }
@ -16,22 +15,22 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
public string Name { get; set; }
public string Type { get; set; }
public string Content { get; set; }
public DeviceModelScriptPath Path { get; set; }
public FilePath Path { get; set; }
public DateTimeOffset Created { get; set; }
public DateTimeOffset Modified { get; set; }
public DeviceModelScript()
public DataFile()
{
this.ETag = string.Empty;
this.Id = string.Empty;
this.Type = ScriptInterpreter.JAVASCRIPT_SCRIPT;
this.Type = string.Empty;
this.Content = string.Empty;
this.Path = DeviceModelScriptPath.Storage;
this.Path = FilePath.Storage;
this.Name = string.Empty;
}
[JsonConverter(typeof(StringEnumConverter))]
public enum DeviceModelScriptPath
public enum FilePath
{
[EnumMember(Value = "Undefined")]
Undefined = 0,

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

@ -133,9 +133,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
public DateTimeOffset Modified { get; set; }
// ActualStartTime is the time when Simulation was started
[JsonProperty(Order = 140)]
[JsonProperty(Order = 150)]
public DateTimeOffset? ActualStartTime { get; set; }
// ReplayFileId is the id of the replay file in storage
[JsonProperty(Order = 160)]
public string ReplayFileId { get; set; }
public Simulation()
{
// When unspecified, a simulation is enabled

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

@ -0,0 +1,174 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Storage;
using Newtonsoft.Json;
using Microsoft.VisualBasic.FileIO;
using FieldType = Microsoft.VisualBasic.FileIO.FieldType;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
public interface IReplayFileService
{
/// <summary>
/// Get a replay file.
/// </summary>
Task<DataFile> GetAsync(string id);
/// <summary>
/// Create a replay file.
/// </summary>
Task<DataFile> InsertAsync(DataFile replayFile);
/// <summary>
/// Delete a replay file.
/// </summary>
Task DeleteAsync(string id);
/// <summary>
/// Validate replay file.
/// </summary>
string ValidateFile(Stream stream);
}
public class ReplayFileService : IReplayFileService
{
private readonly IEngine replayFilesStorage;
private readonly ILogger log;
public ReplayFileService(
IServicesConfig config,
IEngines engines,
IEngine storage,
ILogger logger)
{
this.replayFilesStorage = engines.Build(config.ReplayFilesStorage);
this.log = logger;
}
/// <summary>
/// Delete a device model script.
/// </summary>
public async Task DeleteAsync(string id)
{
try
{
await this.replayFilesStorage.DeleteAsync(id);
}
catch (Exception e)
{
this.log.Error("Something went wrong while deleting the replay file.", () => new { id, e });
throw new ExternalDependencyException("Failed to delete the replay file", e);
}
}
/// <summary>
/// Get a device model script.
/// </summary>
public async Task<DataFile> GetAsync(string id)
{
if (string.IsNullOrEmpty(id))
{
this.log.Error("Simulation script id cannot be empty!");
throw new InvalidInputException("Simulation script id cannot be empty! ");
}
IDataRecord item;
try
{
item = await this.replayFilesStorage.GetAsync(id);
}
catch (ResourceNotFoundException)
{
throw;
}
catch (Exception e)
{
this.log.Error("Unable to load replay file from storage", () => new { id, e });
throw new ExternalDependencyException("Unable to load device model script from storage", e);
}
try
{
var deviceModelScript = JsonConvert.DeserializeObject<DataFile>(item.GetData());
deviceModelScript.ETag = item.GetETag();
return deviceModelScript;
}
catch (Exception e)
{
this.log.Error("Unable to parse device model script loaded from storage", () => new { id, e });
throw new ExternalDependencyException("Unable to parse device model script loaded from storage", e);
}
}
/// <summary>
/// Create a device model script.
/// </summary>
public async Task<DataFile> InsertAsync(DataFile replayFile)
{
replayFile.Created = DateTimeOffset.UtcNow;
replayFile.Modified = replayFile.Created;
if (string.IsNullOrEmpty(replayFile.Id))
{
replayFile.Id = Guid.NewGuid().ToString();
}
this.log.Debug("Creating a new replay file.", () => new { replayFile });
try
{
IDataRecord record = this.replayFilesStorage.BuildRecord(replayFile.Id,
JsonConvert.SerializeObject(replayFile));
var result = await this.replayFilesStorage.CreateAsync(record);
replayFile.ETag = result.GetETag();
}
catch (Exception e)
{
this.log.Error("Failed to insert new replay file into storage",
() => new { replayFile, e });
throw new ExternalDependencyException(
"Failed to insert new replay file into storage", e);
}
return replayFile;
}
/// <summary>
/// Validate replay file
/// </summary>
public string ValidateFile(Stream stream)
{
var reader = new StreamReader(stream);
var file = reader.ReadToEnd();
using (TextFieldParser parser = new TextFieldParser(file))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
try
{
string[] lines = parser.ReadFields();
}
catch (MalformedLineException ex)
{
this.log.Error("Replay file has invalid csv format", () => new { ex });
throw new InvalidInputException("Replay file has invalid csv format", ex);
}
}
}
return file;
}
}
}

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

@ -27,6 +27,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
Config DevicesStorage { get; set; }
Config PartitionsStorage { get; set; }
Config StatisticsStorage { get; set; }
Config ReplayFilesStorage { get; set; }
string DiagnosticsEndpointUrl { get; }
string UserAgent { get; }
bool DevelopmentMode { get; }
@ -116,6 +117,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
public Config PartitionsStorage { get; set; }
public Config ReplayFilesStorage { get; set; }
public string UserAgent { get; set; }
public bool DevelopmentMode { get; set; }

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

@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Upn_Components_Dependencies" Version="1.0.0" />
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
</ItemGroup>
<ItemGroup>

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

@ -85,7 +85,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
{
Program program;
bool isInStorage = string.Equals(script.Path.Trim(),
DeviceModelScript.DeviceModelScriptPath.Storage.ToString(),
DataFile.FilePath.Storage.ToString(),
StringComparison.OrdinalIgnoreCase);
string filename = isInStorage ? script.Id : script.Path;

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

@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.assert" Version="2.4.1" />

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

@ -94,7 +94,7 @@ namespace WebService.Test.v1.Controllers
IFormFile file = this.SetupFileMock();
this.deviceModelScriptsService
.Setup(x => x.InsertAsync(It.IsAny<DeviceModelScript>()))
.Setup(x => x.InsertAsync(It.IsAny<DataFile>()))
.ReturnsAsync(deviceModelScript);
// Act
@ -123,7 +123,7 @@ namespace WebService.Test.v1.Controllers
IFormFile file = this.SetupFileMock();
this.deviceModelScriptsService
.Setup(x => x.UpsertAsync(It.IsAny<DeviceModelScript>()))
.Setup(x => x.UpsertAsync(It.IsAny<DataFile>()))
.ReturnsAsync(deviceModelScript);
// Act
@ -199,23 +199,23 @@ namespace WebService.Test.v1.Controllers
return fileMock.Object;
}
private DeviceModelScript GetDeviceModelScriptById(string id)
private DataFile GetDeviceModelScriptById(string id)
{
return new DeviceModelScript
return new DataFile
{
Id = id,
ETag = "etag",
Path = DeviceModelScript.DeviceModelScriptPath.Storage
Path = DataFile.FilePath.Storage
};
}
private List<DeviceModelScript> GetDeviceModelScripts()
private List<DataFile> GetDeviceModelScripts()
{
return new List<DeviceModelScript>
return new List<DataFile>
{
new DeviceModelScript { Id = "Id_1", ETag = "Etag_1" },
new DeviceModelScript { Id = "Id_2", ETag = "Etag_2" },
new DeviceModelScript { Id = "Id_3", ETag = "Etag_3" }
new DataFile { Id = "Id_1", ETag = "Etag_1" },
new DataFile { Id = "Id_2", ETag = "Etag_2" },
new DataFile { Id = "Id_3", ETag = "Etag_3" }
};
}
}

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

@ -99,6 +99,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
private const string DEVICES_STORAGE_KEY = APPLICATION_KEY + "Storage:Devices:";
private const string PARTITIONS_STORAGE_KEY = APPLICATION_KEY + "Storage:Partitions:";
private const string STATISTICS_STORAGE_KEY = APPLICATION_KEY + "Storage:Statistics:";
private const string REPLAY_FILES_STORAGE_KEY = APPLICATION_KEY + "Storage:ReplayFiles:";
private const string STORAGE_TYPE_KEY = "type";
private const string STORAGE_MAX_PENDING_OPERATIONS = "max_pending_storage_tasks";
@ -285,6 +286,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
PartitionsStorage = GetStorageConfig(configData, PARTITIONS_STORAGE_KEY),
UserAgent = configData.GetString(USER_AGENT_KEY, DEFAULT_USER_AGENT_STRING),
StatisticsStorage = GetStorageConfig(configData, STATISTICS_STORAGE_KEY),
ReplayFilesStorage = GetStorageConfig(configData, REPLAY_FILES_STORAGE_KEY),
DiagnosticsEndpointUrl = configData.GetString(LOGGING_DIAGNOSTICS_URL_KEY),
DevelopmentMode = configData.GetBool(DEBUGGING_DEVELOPMENT_MODE_KEY, false),
DisableSimulationAgent = configData.GetBool(DEBUGGING_DISABLE_SIMULATION_AGENT_KEY, false),

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

@ -230,6 +230,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService
log.Write("Main storage: " + config.ServicesConfig.MainStorage.StorageType);
log.Write("Simulations storage: " + config.ServicesConfig.SimulationsStorage.StorageType);
log.Write("Statistics storage: " + config.ServicesConfig.StatisticsStorage.StorageType);
log.Write("Replay files storage:" + config.ServicesConfig.ReplayFilesStorage.StorageType);
log.Write("Devices storage: " + config.ServicesConfig.DevicesStorage.StorageType);
log.Write("Partitions storage: " + config.ServicesConfig.PartitionsStorage.StorageType);
log.Write("Nodes storage: " + config.ServicesConfig.NodesStorage.StorageType);

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

@ -42,6 +42,7 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="2.1.1" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="2.1.5" />
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PartitioningAgent\PartitioningAgent.csproj" />

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

@ -197,6 +197,22 @@ cosmosdbsql_collection_throughput = 2500
max_pending_storage_tasks = 25
[DeviceSimulationService:Storage:ReplayFiles]
# Type of storage used to store this data
# Possible values (not case sensitive): CosmosDbSql, TableStorage
type = "CosmosDbSql"
# Cosmos DB SQL (prev. known as DocumentDb) connection string,
# Format: AccountEndpoint=https://_____.documents.azure.com:443/;AccountKey=_____;
cosmosdbsql_connstring = "${PCS_STORAGEADAPTER_DOCUMENTDB_CONNSTRING}"
cosmosdbsql_database = "devicesimulation"
cosmosdbsql_collection = "replayFiles"
# CosmosDb throughput, see https://docs.microsoft.com/azure/cosmos-db/request-units
# Default: 400, Recommended: 2500
cosmosdbsql_collection_throughput = 2500
# Max number of concurrent requests when running tasks in parallel
# Default: 25
max_pending_storage_tasks = 25
[DeviceSimulationService:Deployment]
# AAD Domain of the Azure subscription where the Azure IoT Hub is deployed.
# The value is optional because the service can be deployed without a hub.

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

@ -99,13 +99,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
throw new BadRequestException("Wrong content type provided.");
}
var deviceModelScript = new DeviceModelScript();
var deviceModelScript = new DataFile();
try
{
var content = this.javascriptInterpreter.Validate(file.OpenReadStream());
deviceModelScript.Content = content;
deviceModelScript.Name = file.FileName;
deviceModelScript.Type = ScriptInterpreter.JAVASCRIPT_SCRIPT;
}
catch (Exception e)
{
@ -139,7 +140,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
throw new BadRequestException("No ETag provided.");
}
var simulationScript = new DeviceModelScript
var simulationScript = new DataFile
{
ETag = eTag,
Id = id
@ -150,6 +151,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
var reader = new StreamReader(file.OpenReadStream());
simulationScript.Content = reader.ReadToEnd();
simulationScript.Name = file.FileName;
simulationScript.Type = ScriptInterpreter.JAVASCRIPT_SCRIPT;
}
catch (Exception e)
{

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

@ -0,0 +1,105 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Filters;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Helpers;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.ReplayFileApiModel;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers
{
[ExceptionsFilter]
public class ReplayFileController : Controller
{
private const string TEXT_CSV = "text/csv";
private const string TYPE_CSV = "csv";
private readonly ILogger log;
private readonly IReplayFileService replayFileService;
public ReplayFileController(
IReplayFileService replayFileService,
ILogger logger)
{
this.replayFileService = replayFileService;
this.log = logger;
}
[HttpGet(Version.PATH + "/[controller]/{id}")]
public async Task<ReplayFileApiModel> GetAsync(string id)
{
return ReplayFileApiModel.FromServiceModel(await this.replayFileService.GetAsync(id));
}
[HttpPost(Version.PATH + "/[controller]!validate")]
public ActionResult Validate(IFormFile file)
{
try
{
var content = this.replayFileService.ValidateFile(file.OpenReadStream());
}
catch (Exception e)
{
return new JsonResult(new ValidationApiModel
{
IsValid = false,
Messages = new List<string>
{
e.Message
}
})
{ StatusCode = (int) HttpStatusCode.BadRequest };
}
return new JsonResult(new ValidationApiModel()) { StatusCode = (int) HttpStatusCode.OK };
}
[HttpPost(Version.PATH + "/[controller]")]
public async Task<ReplayFileApiModel> PostAsync(IFormFile file)
{
var replayFile = new DataFile();
try
{
var content = this.replayFileService.ValidateFile(file.OpenReadStream());
replayFile.Content = content;
replayFile.Name = file.FileName;
}
catch (Exception e)
{
throw new BadRequestException(e.Message);
}
return ReplayFileApiModel.FromServiceModel(await this.replayFileService.InsertAsync(replayFile));
}
[HttpDelete(Version.PATH + "/[controller]/{id}")]
public async Task DeleteAsync(string id)
{
await this.replayFileService.DeleteAsync(id);
}
private void ValidateInput(IFormFile file)
{
if (file == null)
{
this.log.Warn("No replay data provided");
throw new BadRequestException("No replay data provided.");
}
if (file.ContentType != TEXT_CSV && !file.FileName.EndsWith(".csv"))
{
this.log.Warn("Wrong content type provided. Expected csv file format.", () => new { file.ContentType });
throw new BadRequestException("Wrong content type provided. Expected csv file format.");
}
}
}
}

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

@ -51,21 +51,21 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.Dev
}
// Map API model to service model
public Services.Models.DeviceModelScript ToServiceModel()
public Services.Models.DataFile ToServiceModel()
{
return new Services.Models.DeviceModelScript
return new Services.Models.DataFile
{
ETag = this.ETag,
Id = this.Id,
Type = this.Type,
Path = (Services.Models.DeviceModelScript.DeviceModelScriptPath)Enum.Parse(typeof(Services.Models.DeviceModelScript.DeviceModelScriptPath), this.Path, true),
Path = (Services.Models.DataFile.FilePath)Enum.Parse(typeof(Services.Models.DataFile.FilePath), this.Path, true),
Content = this.Content,
Name = this.Name
};
}
// Map service model to API model
public static DeviceModelScriptApiModel FromServiceModel(Services.Models.DeviceModelScript value)
public static DeviceModelScriptApiModel FromServiceModel(Services.Models.DataFile value)
{
if (value == null) return null;

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

@ -25,7 +25,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
}
// Map service model to API model
public static DeviceModelScriptListModel FromServiceModel(IEnumerable<DeviceModelScript> value)
public static DeviceModelScriptListModel FromServiceModel(IEnumerable<DataFile> value)
{
if (value == null) return null;

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

@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models.ReplayFileApiModel
{
public class ReplayFileApiModel
{
private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:sszzz";
private DateTimeOffset created;
private DateTimeOffset modified;
[JsonProperty(PropertyName = "ETag")]
public string ETag { get; set; }
[JsonProperty(PropertyName = "Id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "Type")]
public string Type { get; set; }
[JsonProperty(PropertyName = "Name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "Content")]
public string Content { get; set; }
[JsonProperty(PropertyName = "Path")]
public string Path { get; set; }
[JsonProperty(PropertyName = "$metadata", Order = 1000)]
public IDictionary<string, string> Metadata => new Dictionary<string, string>
{
{ "$type", "ReplayFile;" + v1.Version.NUMBER },
{ "$uri", "/" + v1.Version.PATH + "/replayfile/" + this.Id },
{ "$created", this.created.ToString(DATE_FORMAT) },
{ "$modified", this.modified.ToString(DATE_FORMAT) }
};
public ReplayFileApiModel()
{
this.ETag = string.Empty;
this.Id = string.Empty;
this.Type = string.Empty;
this.Content = string.Empty;
this.Path = string.Empty;
this.Name = string.Empty;
}
// Map API model to service model
public Services.Models.DataFile ToServiceModel()
{
return new Services.Models.DataFile
{
ETag = this.ETag,
Id = this.Id,
Type = this.Type,
Path = (Services.Models.DataFile.FilePath)Enum.Parse(typeof(Services.Models.DataFile.FilePath), this.Path, true),
Content = this.Content,
Name = this.Name,
};
}
// Map service model to API model
public static ReplayFileApiModel FromServiceModel(Services.Models.DataFile value)
{
if (value == null) return null;
var result = new ReplayFileApiModel
{
ETag = value.ETag,
Id = value.Id,
Type = value.Type,
created = value.Created,
modified = value.Modified,
Path = value.Path.ToString(),
Content = value.Content,
Name = value.Name
};
return result;
}
}
}

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

@ -9,4 +9,4 @@ LICENSE @miwolfms @tommo-ms @saixiaohui
scripts/ @miwolfms @tommo-ms @saixiaohui
WebService/Auth/ @miwolfms @tommo-ms @saixiaohui
Services/Runtime/ @miwolfms @tommo-ms @saixiaohui
Services/Runtime/ @miwolfms @tommo-ms @saixiaohui