Save simulations in the storage (#32)

* Store simulations using the storage adapter
* Implement device bootstrap scenario: when a new device is created, mark it as a simulated device and report some properties like device type, messages schema, and initial location.
* Remove dependency on IoT Hub manager and access IoT Hub directly
* Refactor state machine to reduce complexity and reuse code
* Add launch settings for Visual Studio
* Remove env var used for the web service TCP port
* Improve logging of exceptions to avoid log flooding
* Fix messages format, to always use the “_unit” convention
* Add JSON config checks
This commit is contained in:
Devis Lucato 2017-08-15 10:39:11 -07:00 коммит произвёл GitHub
Родитель abbd305492
Коммит f2c8d6e92d
29 изменённых файлов: 602 добавлений и 287 удалений

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

@ -6,7 +6,9 @@ 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.StorageAdapter;
using Moq;
using Newtonsoft.Json;
using Services.Test.helpers;
using Xunit;
using Xunit.Abstractions;
@ -19,19 +21,21 @@ namespace Services.Test
/// <summary>The test logger</summary>
private readonly ITestOutputHelper log;
private const string StorageCollection = "simulations";
private const string SimulationId = "1";
private readonly Mock<IDeviceTypes> deviceTypes;
private readonly Mock<IStorageAdapterClient> storage;
private readonly Mock<ILogger> logger;
private readonly Simulations target;
private readonly List<DeviceType> types;
private readonly Mock<ILogger> logger;
public SimulationsTest(ITestOutputHelper log)
{
this.log = log;
var tempStorage = Guid.NewGuid() + ".json";
this.log.WriteLine("Temporary simulations storage: " + tempStorage);
this.deviceTypes = new Mock<IDeviceTypes>();
this.storage = new Mock<IStorageAdapterClient>();
this.logger = new Mock<ILogger>();
this.types = new List<DeviceType>
@ -42,15 +46,17 @@ namespace Services.Test
new DeviceType { Id = "AA" }
};
this.target = new Simulations(this.deviceTypes.Object, this.logger.Object);
this.target.ChangeStorageFile(tempStorage);
this.target = new Simulations(this.deviceTypes.Object, this.storage.Object, this.logger.Object);
}
[Fact, Trait(Constants.Type, Constants.UnitTest)]
public void InitialListIsEmpty()
{
// Arrange
this.ThereAreNoSimulationsInTheStorage();
// Act
IList<SimulationModel> list = this.target.GetList();
var list = this.target.GetListAsync().Result;
// Assert
Assert.Equal(0, list.Count);
@ -60,10 +66,11 @@ namespace Services.Test
public void InitialMetadataAfterCreation()
{
// Arrange
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
this.ThereAreNoSimulationsInTheStorage();
this.ThereAreSomeDeviceTypes();
// Act
SimulationModel result = this.target.Insert(new SimulationModel(), "default");
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
// Assert
Assert.Equal(1, result.Version);
@ -75,10 +82,11 @@ namespace Services.Test
{
// Arrange
const int defaultDeviceCount = 2;
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
this.ThereAreNoSimulationsInTheStorage();
this.ThereAreSomeDeviceTypes();
// Act
SimulationModel result = this.target.Insert(new SimulationModel(), "default");
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
// Assert
Assert.Equal(this.types.Count, result.DeviceTypes.Count);
@ -93,10 +101,11 @@ namespace Services.Test
public void CreateSimulationWithoutId()
{
// Arrange
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
this.ThereAreNoSimulationsInTheStorage();
this.ThereAreSomeDeviceTypes();
// Act
SimulationModel result = this.target.Insert(new SimulationModel(), "default");
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
// Assert
Assert.NotEmpty(result.Id);
@ -106,11 +115,12 @@ namespace Services.Test
public void CreateSimulationWithId()
{
// Arrange
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
this.ThereAreSomeDeviceTypes();
this.ThereAreNoSimulationsInTheStorage();
// Act
var simulation = new SimulationModel { Id = "123" };
SimulationModel result = this.target.Insert(simulation, "default");
SimulationModel result = this.target.InsertAsync(simulation, "default").Result;
// Assert
Assert.Equal(simulation.Id, result.Id);
@ -120,52 +130,58 @@ namespace Services.Test
public void CreateWithInvalidTemplate()
{
// Act + Assert
Assert.Throws<InvalidInputException>(() => this.target.Insert(new SimulationModel(), "foo"));
Assert.ThrowsAsync<InvalidInputException>(
() => this.target.InsertAsync(new SimulationModel(), "mytemplate"));
}
[Fact, Trait(Constants.Type, Constants.UnitTest)]
public void CreatingMultipleSimulationsIsNotAllowed()
{
// Arrange
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
this.target.Insert(new SimulationModel(), "default");
this.ThereAreSomeDeviceTypes();
this.ThereIsAnEnabledSimulationInTheStorage();
// Act + Assert
var s = new SimulationModel { Id = Guid.NewGuid().ToString(), Enabled = false };
Assert.Throws<ConflictingResourceException>(() => this.target.Insert(s));
Assert.Throws<ConflictingResourceException>(() => this.target.Upsert(s));
Assert.ThrowsAsync<ConflictingResourceException>(() => this.target.InsertAsync(s));
Assert.ThrowsAsync<ConflictingResourceException>(() => this.target.UpsertAsync(s));
}
[Fact, Trait(Constants.Type, Constants.UnitTest)]
public void CreatedSimulationsAreStored()
{
// Arrange
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
this.ThereAreSomeDeviceTypes();
this.ThereAreNoSimulationsInTheStorage();
// Act
var simulation = new SimulationModel { Id = Guid.NewGuid().ToString(), Enabled = false };
this.target.Insert(simulation, "default");
var result = this.target.Get(simulation.Id);
this.target.InsertAsync(simulation, "default").Wait();
// Assert
Assert.Equal(simulation.Id, result.Id);
Assert.Equal(simulation.Enabled, result.Enabled);
this.storage.Verify(
x => x.UpdateAsync(StorageCollection, SimulationId, It.IsAny<string>(), "*"));
}
[Fact, Trait(Constants.Type, Constants.UnitTest)]
public void SimulationsCanBeUpserted()
{
// Arrange
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
this.ThereAreSomeDeviceTypes();
this.ThereAreNoSimulationsInTheStorage();
// Act
var simulation = new SimulationModel { Id = Guid.NewGuid().ToString(), Enabled = false };
this.target.Upsert(simulation, "default");
var result = this.target.Get(simulation.Id);
var simulation = new SimulationModel
{
Id = SimulationId,
Enabled = false,
Etag = "2345213461"
};
this.target.UpsertAsync(simulation).Wait();
// Assert
Assert.Equal(simulation.Id, result.Id);
Assert.Equal(simulation.Enabled, result.Enabled);
this.storage.Verify(
x => x.UpdateAsync(StorageCollection, SimulationId, It.IsAny<string>(), simulation.Etag));
}
[Fact, Trait(Constants.Type, Constants.UnitTest)]
@ -174,10 +190,10 @@ namespace Services.Test
// Act
var s1 = new SimulationModel();
var s2 = new SimulationModel();
this.target.Insert(s1);
this.target.InsertAsync(s1);
// Act + Assert
Assert.Throws<InvalidInputException>(() => this.target.Upsert(s2));
Assert.ThrowsAsync<InvalidInputException>(() => this.target.UpsertAsync(s2));
}
[Fact, Trait(Constants.Type, Constants.UnitTest)]
@ -188,11 +204,45 @@ namespace Services.Test
var id = Guid.NewGuid().ToString();
var s1 = new SimulationModel { Id = id, Enabled = false };
this.target.Upsert(s1);
this.target.UpsertAsync(s1);
// Act + Assert
var s1updated = new SimulationModel { Id = id, Enabled = true };
Assert.Throws<ResourceOutOfDateException>(() => this.target.Upsert(s1updated));
Assert.ThrowsAsync<ResourceOutOfDateException>(() => this.target.UpsertAsync(s1updated));
}
private void ThereAreSomeDeviceTypes()
{
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
}
private void ThereAreNoSimulationsInTheStorage()
{
this.storage.Setup(x => x.GetAllAsync(StorageCollection)).ReturnsAsync(new ValueListApiModel());
}
private void ThereIsAnEnabledSimulationInTheStorage()
{
var simulation = new SimulationModel
{
Id = SimulationId,
Created = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
Modified = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
Etag = "etag0",
Enabled = true,
Version = 1
};
var list = new ValueListApiModel();
var value = new ValueApiModel
{
Key = SimulationId,
Data = JsonConvert.SerializeObject(simulation),
ETag = simulation.Etag
};
list.Items.Add(value);
this.storage.Setup(x => x.GetAllAsync(StorageCollection)).ReturnsAsync(list);
}
}
}

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

@ -63,8 +63,15 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency
public void Stop()
{
this.timer?.Change(Timeout.Infinite, Timeout.Infinite);
this.timer?.Dispose();
try
{
this.timer?.Change(Timeout.Infinite, Timeout.Infinite);
this.timer?.Dispose();
}
catch (ObjectDisposedException e)
{
this.log.Info("The timer was already disposed.", () => new { e });
}
}
}
}

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

@ -27,8 +27,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
private readonly string ioTHubHostName;
public Devices(
ILogger logger,
IServicesConfig config)
IServicesConfig config,
ILogger logger)
{
this.log = logger;
this.registry = RegistryManager.CreateFromConnectionString(config.IoTHubConnString);

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

@ -23,6 +23,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions
{
}
public ExternalDependencyException(Exception innerException)
: base("An unexpected error happened while using an external dependency.", innerException)
{
}
public ExternalDependencyException(string message, Exception innerException)
: base(message, innerException)
{

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

@ -96,7 +96,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
{
StatusCode = response.StatusCode,
Headers = response.Headers,
Content = await response.Content.ReadAsStringAsync(),
Content = await response.Content.ReadAsStringAsync()
};
}
}
@ -108,13 +108,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
errorMessage += " - " + e.InnerException.Message;
}
this.log.Error("Request failed", () => new
{
ExceptionMessage = e.Message,
InnerExceptionType = e.InnerException != null ? e.InnerException.GetType().FullName : "",
InnerExceptionMessage = e.InnerException != null ? e.InnerException.Message : "",
errorMessage
});
this.log.Error("Request failed", () => new { errorMessage, e });
return new HttpResponse
{
@ -122,6 +116,26 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
Content = errorMessage
};
}
catch (TaskCanceledException e)
{
this.log.Error("Request failed", () => new { Message = e.Message + " The request timed out, the endpoint might be unreachable.", e });
return new HttpResponse
{
StatusCode = 0,
Content = e.Message + " The endpoint might be unreachable."
};
}
catch (Exception e)
{
this.log.Error("Request failed", () => new { e.Message, e });
return new HttpResponse
{
StatusCode = 0,
Content = e.Message
};
}
}
}

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

@ -20,27 +20,27 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
HttpContent Content { get; }
void AddHeader(string name, string value);
IHttpRequest AddHeader(string name, string value);
void SetUriFromString(string uri);
IHttpRequest SetUriFromString(string uri);
void SetContent(string content);
IHttpRequest SetContent(string content);
void SetContent(string content, Encoding encoding);
IHttpRequest SetContent(string content, Encoding encoding);
void SetContent(string content, Encoding encoding, string mediaType);
IHttpRequest SetContent(string content, Encoding encoding, string mediaType);
void SetContent(string content, Encoding encoding, MediaTypeHeaderValue mediaType);
IHttpRequest SetContent(string content, Encoding encoding, MediaTypeHeaderValue mediaType);
void SetContent(StringContent stringContent);
IHttpRequest SetContent(StringContent stringContent);
void SetContent<T>(T sourceObject);
IHttpRequest SetContent<T>(T sourceObject);
void SetContent<T>(T sourceObject, Encoding encoding);
IHttpRequest SetContent<T>(T sourceObject, Encoding encoding);
void SetContent<T>(T sourceObject, Encoding encoding, string mediaType);
IHttpRequest SetContent<T>(T sourceObject, Encoding encoding, string mediaType);
void SetContent<T>(T sourceObject, Encoding encoding, MediaTypeHeaderValue mediaType);
IHttpRequest SetContent<T>(T sourceObject, Encoding encoding, MediaTypeHeaderValue mediaType);
}
public class HttpRequest : IHttpRequest
@ -76,7 +76,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
this.SetUriFromString(uri);
}
public void AddHeader(string name, string value)
public IHttpRequest AddHeader(string name, string value)
{
if (!this.Headers.TryAddWithoutValidation(name, value))
{
@ -87,60 +87,66 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
this.ContentType = new MediaTypeHeaderValue(value);
}
return this;
}
public void SetUriFromString(string uri)
public IHttpRequest SetUriFromString(string uri)
{
this.Uri = new Uri(uri);
return this;
}
public void SetContent(string content)
public IHttpRequest SetContent(string content)
{
this.SetContent(content, this.defaultEncoding, this.defaultMediaType);
return this.SetContent(content, this.defaultEncoding, this.defaultMediaType);
}
public void SetContent(string content, Encoding encoding)
public IHttpRequest SetContent(string content, Encoding encoding)
{
this.SetContent(content, encoding, this.defaultMediaType);
return this.SetContent(content, encoding, this.defaultMediaType);
}
public void SetContent(string content, Encoding encoding, string mediaType)
public IHttpRequest SetContent(string content, Encoding encoding, string mediaType)
{
this.SetContent(content, encoding, new MediaTypeHeaderValue(mediaType));
return this.SetContent(content, encoding, new MediaTypeHeaderValue(mediaType));
}
public void SetContent(string content, Encoding encoding, MediaTypeHeaderValue mediaType)
public IHttpRequest SetContent(string content, Encoding encoding, MediaTypeHeaderValue mediaType)
{
this.requestContent.Content = new StringContent(content, encoding, mediaType.MediaType);
this.ContentType = mediaType;
return this;
}
public void SetContent(StringContent stringContent)
public IHttpRequest SetContent(StringContent stringContent)
{
this.requestContent.Content = stringContent;
this.ContentType = stringContent.Headers.ContentType;
return this;
}
public void SetContent<T>(T sourceObject)
public IHttpRequest SetContent<T>(T sourceObject)
{
this.SetContent(sourceObject, this.defaultEncoding, this.defaultMediaType);
return this.SetContent(sourceObject, this.defaultEncoding, this.defaultMediaType);
}
public void SetContent<T>(T sourceObject, Encoding encoding)
public IHttpRequest SetContent<T>(T sourceObject, Encoding encoding)
{
this.SetContent(sourceObject, encoding, this.defaultMediaType);
return this.SetContent(sourceObject, encoding, this.defaultMediaType);
}
public void SetContent<T>(T sourceObject, Encoding encoding, string mediaType)
public IHttpRequest SetContent<T>(T sourceObject, Encoding encoding, string mediaType)
{
this.SetContent(sourceObject, encoding, new MediaTypeHeaderValue(mediaType));
return this.SetContent(sourceObject, encoding, new MediaTypeHeaderValue(mediaType));
}
public void SetContent<T>(T sourceObject, Encoding encoding, MediaTypeHeaderValue mediaType)
public IHttpRequest SetContent<T>(T sourceObject, Encoding encoding, MediaTypeHeaderValue mediaType)
{
var content = JsonConvert.SerializeObject(sourceObject, Formatting.None);
this.requestContent.Content = new StringContent(content, encoding, mediaType.MediaType);
this.ContentType = mediaType;
return this;
}
}
}

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

@ -10,7 +10,20 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
HttpStatusCode StatusCode { get; }
HttpResponseHeaders Headers { get; }
string Content { get; }
bool IsSuccess { get; }
bool IsError { get; }
bool IsIncomplete { get; }
bool IsNonRetriableError { get; }
bool IsRetriableError { get; }
bool IsBadRequest { get; }
bool IsUnauthorized { get; }
bool IsForbidden { get; }
bool IsNotFound { get; }
bool IsTimeout { get; }
bool IsConflict { get; }
bool IsServerError { get; }
bool IsServiceUnavailable { get; }
}
public class HttpResponse : IHttpResponse
@ -35,8 +48,31 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http
public HttpResponseHeaders Headers { get; internal set; }
public string Content { get; internal set; }
public bool IsSuccess => (int) this.StatusCode >= 200 && (int) this.StatusCode <= 299;
public bool IsError => (int) this.StatusCode >= 400 || (int) this.StatusCode == 0;
public bool IsIncomplete
{
get
{
var c = (int) this.StatusCode;
return (c >= 100 && c <= 199) || (c >= 300 && c <= 399);
}
}
public bool IsNonRetriableError => IsError && !IsRetriableError;
public bool IsRetriableError => this.StatusCode == HttpStatusCode.NotFound ||
this.StatusCode == HttpStatusCode.RequestTimeout ||
(int) this.StatusCode == TooManyRequests;
public bool IsBadRequest => (int) this.StatusCode == 400;
public bool IsUnauthorized => (int) this.StatusCode == 401;
public bool IsForbidden => (int) this.StatusCode == 403;
public bool IsNotFound => (int) this.StatusCode == 404;
public bool IsTimeout => (int) this.StatusCode == 408;
public bool IsConflict => (int) this.StatusCode == 409;
public bool IsServerError => (int) this.StatusCode >= 500;
public bool IsServiceUnavailable => (int) this.StatusCode == 503;
}
}

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

@ -1,12 +0,0 @@
Service layer
=============
## Guidelines
* The service layer is responsible for the business logic of the microservice
and for dealing with external dependencies like storage, IoT Hub, etc.
* The service layer has no knowledge of the web service or other entry points.
## Conventions
* Configuration is injected into the service layer by the entry point projects.

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

@ -9,6 +9,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
string DeviceTypesFolder { get; set; }
string DeviceTypesScriptsFolder { get; set; }
string IoTHubConnString { get; set; }
string StorageAdapterApiUrl { get; set; }
int StorageAdapterApiTimeout { get; set; }
}
// TODO: test Windows/Linux folder separator
@ -31,6 +33,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
public string IoTHubConnString { get; set; }
public string StorageAdapterApiUrl { get; set; }
public int StorageAdapterApiTimeout { get; set; }
private string NormalizePath(string path)
{
return path

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

@ -2,78 +2,67 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency;
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 Newtonsoft.Json;
// TODO: use real storage
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
public interface ISimulations
{
IList<Models.Simulation> GetList();
Models.Simulation Get(string id);
Models.Simulation Insert(Models.Simulation simulation, string template = "");
Models.Simulation Upsert(Models.Simulation simulation, string template = "");
Models.Simulation Merge(SimulationPatch patch);
Task<IList<Models.Simulation>> GetListAsync();
Task<Models.Simulation> GetAsync(string id);
Task<Models.Simulation> InsertAsync(Models.Simulation simulation, string template = "");
Task<Models.Simulation> UpsertAsync(Models.Simulation simulation);
Task<Models.Simulation> MergeAsync(SimulationPatch patch);
}
/// <summary>
/// Note: Since we don't have a configuration service yet, data is
/// stored to a JSON file.
/// </summary>
public class Simulations : ISimulations
{
private string tempStorageFile = "simulations-storage.json";
private string tempStoragePath;
private const string StorageCollection = "simulations";
private const string SimulationId = "1";
private const int DevicesPerTypeInDefaultTemplate = 2;
private readonly IDeviceTypes deviceTypes;
private readonly IStorageAdapterClient storage;
private readonly ILogger log;
public Simulations(
IDeviceTypes deviceTypes,
IStorageAdapterClient storage,
ILogger logger)
{
this.deviceTypes = deviceTypes;
this.storage = storage;
this.log = logger;
this.SetupTempStoragePath();
this.CreateStorageIfMissing();
}
// TODO: remove this method and use mocks when we have a storage service
public void ChangeStorageFile(string filename)
public async Task<IList<Models.Simulation>> GetListAsync()
{
this.tempStorageFile = filename;
this.SetupTempStoragePath();
this.CreateStorageIfMissing();
}
public IList<Models.Simulation> GetList()
{
this.CreateStorageIfMissing();
var json = File.ReadAllText(this.tempStoragePath);
return JsonConvert.DeserializeObject<List<Models.Simulation>>(json);
}
public Models.Simulation Get(string id)
{
var simulations = this.GetList();
foreach (var s in simulations)
var data = await this.storage.GetAllAsync(StorageCollection);
var result = new List<Models.Simulation>();
foreach (var item in data.Items)
{
if (s.Id == id) return s;
var simulation = JsonConvert.DeserializeObject<Models.Simulation>(item.Data);
simulation.Etag = item.ETag;
result.Add(simulation);
}
this.log.Warn("Simulation not found", () => new { id });
throw new ResourceNotFoundException();
return result;
}
public Models.Simulation Insert(Models.Simulation simulation, string template = "")
public async Task<Models.Simulation> GetAsync(string id)
{
var item = await this.storage.GetAsync(StorageCollection, id);
var simulation = JsonConvert.DeserializeObject<Models.Simulation>(item.Data);
simulation.Etag = item.ETag;
return simulation;
}
public async Task<Models.Simulation> InsertAsync(Models.Simulation simulation, string template = "")
{
// TODO: complete validation
if (!string.IsNullOrEmpty(template) && template.ToLowerInvariant() != "default")
@ -82,24 +71,16 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
throw new InvalidInputException("Unknown template name. Try 'default'.");
}
var simulations = this.GetList();
// Only one simulation per deployment
var simulations = await this.GetListAsync();
if (simulations.Count > 0)
{
this.log.Warn("There is already a simulation",
() => new { Existing = simulations.First().Id });
this.log.Warn("There is already a simulation", () => { });
throw new ConflictingResourceException(
"There is already a simulation. Only one simulation can be created.");
}
// The ID is not empty when using PUT
if (string.IsNullOrEmpty(simulation.Id))
{
simulation.Id = Guid.NewGuid().ToString();
}
// Note: forcing the ID because only one simulation can be created
simulation.Id = SimulationId;
simulation.Created = DateTimeOffset.UtcNow;
simulation.Modified = simulation.Created;
simulation.Version = 1;
@ -114,136 +95,97 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
simulation.DeviceTypes.Add(new Models.Simulation.DeviceTypeRef
{
Id = type.Id,
Count = 2
Count = DevicesPerTypeInDefaultTemplate
});
}
}
this.WriteToStorage(simulation);
// Note: using UpdateAsync because the service generates the ID
await this.storage.UpdateAsync(
StorageCollection,
SimulationId,
JsonConvert.SerializeObject(simulation),
"*");
return simulation;
}
public Models.Simulation Upsert(Models.Simulation simulation, string template = "")
{
// TODO: complete validation
if (string.IsNullOrEmpty(simulation.Id))
{
this.log.Warn("Missing ID", () => new { simulation });
throw new InvalidInputException("Missing ID");
}
var simulations = this.GetList();
if (simulations.Count == 0)
{
return this.Insert(simulation, template);
}
// Note: only one simulation per deployment
if (simulations[0].Id != simulation.Id)
{
this.log.Warn("There is already a simulation",
() => new { Existing = simulations[0].Id, Provided = simulation.Id });
throw new ConflictingResourceException(
"There is already a simulation. Only one simulation can be created.");
}
if (simulations[0].Etag != simulation.Etag)
{
this.log.Warn("Etag mismatch",
() => new { Existing = simulations[0].Etag, Provided = simulation.Etag });
throw new ResourceOutOfDateException(
"Etag mismatch: the resource has been updated by another client.");
}
simulation.Modified = DateTimeOffset.UtcNow;
simulation.Version += 1;
this.WriteToStorage(simulation);
return simulation;
}
public Models.Simulation Merge(SimulationPatch patch)
{
var simulations = this.GetList();
if (simulations.Count == 0 || simulations[0].Id != patch.Id)
{
this.log.Warn("The simulation doesn't exist.",
() => new { ExistingSimulations = simulations.Count, IdProvided = patch.Id });
throw new ResourceNotFoundException("The simulation doesn't exist.");
}
if (simulations[0].Etag != patch.Etag)
{
this.log.Warn("Etag mismatch",
() => new { Existing = simulations[0].Etag, Provided = patch.Etag });
throw new ResourceOutOfDateException(
"Etag mismatch: the resource has been updated by another client.");
}
var simulation = simulations[0];
var resourceChanged = false;
if (patch.Enabled.HasValue && patch.Enabled.Value != simulation.Enabled)
{
simulation.Enabled = patch.Enabled.Value;
resourceChanged = true;
}
if (resourceChanged)
{
simulation.Modified = DateTimeOffset.UtcNow;
simulation.Version += 1;
this.WriteToStorage(simulation);
}
return simulation;
}
private void WriteToStorage(Models.Simulation simulation)
{
simulation.Etag = Etags.NewEtag();
var data = new List<Models.Simulation> { simulation };
File.WriteAllText(this.tempStoragePath, JsonConvert.SerializeObject(data));
}
/// <summary>
/// While working within an IDE we need to share the data used by the
/// web service and the data used by the simulation agent, i.e. when
/// running the web service and the simulation agent from the IDE there
/// are two "Services/data" folders, one in each entry point. Thus the
/// simulation agent would not see the temporary storage written by the
/// web service. By using the user/system temp folder, we make sure the
/// storage is shared by the two processes when using an IDE.
/// Upsert the simulation. The logic works under the assumption that
/// there is only one simulation with id "1".
/// </summary>
private void SetupTempStoragePath()
public async Task<Models.Simulation> UpsertAsync(Models.Simulation simulation)
{
var tempFolder = Path.GetTempPath();
// In some cases GetTempPath returns a path under "/var/folders/"
// in which case we opt for /tmp/. Note: this is temporary until
// we use a real storage service.
if (Path.DirectorySeparatorChar == '/' && Directory.Exists("/tmp/"))
if (simulation.Id != SimulationId)
{
tempFolder = "/tmp/";
this.log.Warn("Invalid simulation ID. Only one simulation is allowed", () => { });
throw new InvalidInputException("Invalid simulation ID. Use ID '" + SimulationId + "'.");
}
this.tempStoragePath = (tempFolder + Path.DirectorySeparatorChar + this.tempStorageFile)
.Replace(Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar,
Path.DirectorySeparatorChar.ToString());
var simulations = await this.GetListAsync();
if (simulations.Count > 0)
{
//simulation.Modified = DateTimeOffset.UtcNow;
//simulation.Version = simulations.First().Version + 1;
this.log.Error("Simulations cannot be modified via PUT. Use PATCH to start/stop the simulation.", () => { });
throw new InvalidInputException("Simulations cannot be modified via PUT. Use PATCH to start/stop the simulation.");
}
this.log.Info("Temporary simulations storage: " + this.tempStoragePath, () => { });
// Note: forcing the ID because only one simulation can be created
simulation.Id = SimulationId;
simulation.Created = DateTimeOffset.UtcNow;
simulation.Modified = simulation.Created;
simulation.Version = 1;
await this.storage.UpdateAsync(
StorageCollection,
SimulationId,
JsonConvert.SerializeObject(simulation),
simulation.Etag);
return simulation;
}
private void CreateStorageIfMissing()
public async Task<Models.Simulation> MergeAsync(SimulationPatch patch)
{
if (!File.Exists(this.tempStoragePath))
if (patch.Id != SimulationId)
{
var data = new List<Models.Simulation>();
File.WriteAllText(this.tempStoragePath, JsonConvert.SerializeObject(data));
this.log.Warn("Invalid simulation ID.", () => new { patch.Id });
throw new InvalidInputException("Invalid simulation ID. Use ID '" + SimulationId + "'.");
}
var item = await this.storage.GetAsync(StorageCollection, patch.Id);
var simulation = JsonConvert.DeserializeObject<Models.Simulation>(item.Data);
simulation.Etag = item.ETag;
// Even when there's nothing to do, verify the etag mismatch
if (patch.Etag != simulation.Etag)
{
this.log.Warn("Etag mismatch",
() => new { Current = simulation.Etag, Provided = patch.Etag });
throw new ConflictingResourceException(
$"The ETag provided doesn't match the current resource ETag ({simulation.Etag}).");
}
if (!patch.Enabled.HasValue || patch.Enabled.Value == simulation.Enabled)
{
// Nothing to do
return simulation;
}
simulation.Enabled = patch.Enabled.Value;
simulation.Modified = DateTimeOffset.UtcNow;
simulation.Version += 1;
item = await this.storage.UpdateAsync(
StorageCollection,
SimulationId,
JsonConvert.SerializeObject(simulation),
patch.Etag);
simulation.Etag = item.ETag;
return simulation;
}
}
}

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

@ -0,0 +1,139 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter;
using Newtonsoft.Json;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
public interface IStorageAdapterClient
{
Task<ValueListApiModel> GetAllAsync(string collectionId);
Task<ValueApiModel> GetAsync(string collectionId, string key);
Task<ValueApiModel> CreateAsync(string collectionId, string value);
Task<ValueApiModel> UpdateAsync(string collectionId, string key, string value, string etag);
Task DeleteAsync(string collectionId, string key);
}
// TODO: handle retriable errors
public class StorageAdapterClient : IStorageAdapterClient
{
// TODO: make it configurable, default to false
private const bool AllowInsecureSSLServer = true;
private readonly IHttpClient httpClient;
private readonly ILogger logger;
private readonly string serviceUri;
private readonly int timeout;
public StorageAdapterClient(
IHttpClient httpClient,
IServicesConfig config,
ILogger logger)
{
this.httpClient = httpClient;
this.logger = logger;
this.serviceUri = config.StorageAdapterApiUrl;
this.timeout = config.StorageAdapterApiTimeout;
}
public async Task<ValueListApiModel> GetAllAsync(string collectionId)
{
var response = await httpClient.GetAsync(
this.PrepareRequest($"collections/{collectionId}/values"));
ThrowIfError(response, collectionId, "");
return JsonConvert.DeserializeObject<ValueListApiModel>(response.Content);
}
public async Task<ValueApiModel> GetAsync(string collectionId, string key)
{
var response = await httpClient.GetAsync(
this.PrepareRequest($"collections/{collectionId}/values/{key}"));
ThrowIfError(response, collectionId, key);
return JsonConvert.DeserializeObject<ValueApiModel>(response.Content);
}
public async Task<ValueApiModel> CreateAsync(string collectionId, string value)
{
var response = await httpClient.PostAsync(
this.PrepareRequest($"collections/{collectionId}/values",
new ValueApiModel { Data = value }));
ThrowIfError(response, collectionId, "");
return JsonConvert.DeserializeObject<ValueApiModel>(response.Content);
}
public async Task<ValueApiModel> UpdateAsync(string collectionId, string key, string value, string etag)
{
var response = await httpClient.PutAsync(
this.PrepareRequest($"collections/{collectionId}/values/{key}",
new ValueApiModel { Data = value, ETag = etag }));
ThrowIfError(response, collectionId, key);
return JsonConvert.DeserializeObject<ValueApiModel>(response.Content);
}
public async Task DeleteAsync(string collectionId, string key)
{
var response = await httpClient.DeleteAsync(
this.PrepareRequest($"collections/{collectionId}/values/{key}"));
ThrowIfError(response, collectionId, key);
}
private HttpRequest PrepareRequest(string path, ValueApiModel content = null)
{
var request = new HttpRequest();
request.AddHeader(HttpRequestHeader.Accept.ToString(), "application/json");
request.AddHeader(HttpRequestHeader.CacheControl.ToString(), "no-cache");
request.AddHeader(HttpRequestHeader.Referer.ToString(), "Device Simulation " + this.GetType().FullName);
request.SetUriFromString($"{this.serviceUri}/{path}");
request.Options.EnsureSuccess = false;
request.Options.Timeout = this.timeout;
if (this.serviceUri.ToLowerInvariant().StartsWith("https:"))
{
request.Options.AllowInsecureSSLServer = AllowInsecureSSLServer;
}
if (content != null)
{
request.SetContent(content);
}
return request;
}
private void ThrowIfError(IHttpResponse response, string collectionId, string key)
{
if (response.StatusCode == HttpStatusCode.NotFound)
{
throw new ResourceNotFoundException($"Resource {collectionId}/{key} not found.");
}
if (response.StatusCode == HttpStatusCode.Conflict)
{
throw new ConflictingResourceException(
$"Resource {collectionId}/{key} out of date. Reload the resource and retry.");
}
if (response.IsError)
{
throw new ExternalDependencyException(
new HttpRequestException($"Storage request error: status code {response.StatusCode}"));
}
}
}
}

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

@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
{
public class ValueApiModel
{
[JsonProperty("Key")]
public string Key { get; set; }
[JsonProperty("Data")]
public string Data { get; set; }
[JsonProperty("ETag")]
public string ETag { get; set; }
[JsonProperty("$metadata")]
public Dictionary<string, string> Metadata;
public ValueApiModel()
{
this.Metadata = new Dictionary<string, string>();
}
}
}

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

@ -0,0 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Newtonsoft.Json;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter
{
public class ValueListApiModel
{
public List<ValueApiModel> Items { get; set; }
[JsonProperty("$metadata")]
public Dictionary<string, string> Metadata { get; set; }
public ValueListApiModel()
{
this.Items = new List<ValueApiModel>();
}
}
}

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

@ -17,7 +17,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
// Print some useful information at bootstrap time
PrintBootstrapInfo(container);
container.Resolve<ISimulation>().Run();
container.Resolve<ISimulation>().RunAsync().Wait();
}
private static void PrintBootstrapInfo(IContainer container)

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

@ -23,6 +23,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Runtime
private const string DeviceTypesScriptsFolderKey = ApplicationKey + "device_types_scripts_folder";
private const string IoTHubConnStringKey = ApplicationKey + "iothub_connstring";
private const string StorageAdapterKey = "storageadapter:";
private const string StorageAdapterApiUrlKey = StorageAdapterKey + "webservice_url";
private const string StorageAdapterApiTimeoutKey = StorageAdapterKey + "webservice_timeout";
/// <summary>Service layer configuration</summary>
public IServicesConfig ServicesConfig { get; }
@ -50,7 +54,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Runtime
{
DeviceTypesFolder = MapRelativePath(configData.GetString(DeviceTypesFolderKey)),
DeviceTypesScriptsFolder = MapRelativePath(configData.GetString(DeviceTypesScriptsFolderKey)),
IoTHubConnString = connstring
IoTHubConnString = connstring,
StorageAdapterApiUrl = configData.GetString(StorageAdapterApiUrlKey),
StorageAdapterApiTimeout = configData.GetInt(StorageAdapterApiTimeoutKey)
};
}

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

@ -373,5 +373,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
return JsonConvert.DeserializeObject<T>(
JsonConvert.SerializeObject(source));
}
private class TelemetryContext
{
public DeviceActor Self { get; set; }
public DeviceType.DeviceTypeMessage Message { get; set; }
}
}
}

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

@ -1,7 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
@ -9,7 +11,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
{
public interface ISimulation
{
void Run();
Task RunAsync();
}
public class Simulation : ISimulation
@ -30,14 +32,24 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
this.runner = runner;
}
public void Run()
public async Task RunAsync()
{
this.log.Info("Simulation Agent running", () => { });
// Keep running, checking if the simulation state changes
while (true)
{
var simulation = this.simulations.GetList().FirstOrDefault();
Services.Models.Simulation simulation = null;
try
{
simulation = (await this.simulations.GetListAsync()).FirstOrDefault();
}
catch (Exception e)
{
this.log.Error("Unable to load simulation", () => new { e });
}
if (simulation != null && simulation.Enabled)
{
this.runner.Start(simulation);

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

@ -5,3 +5,7 @@ iothub_connstring = "${PCS_IOTHUB_CONNSTRING}"
# which can be mounted to inject custom device types and scripts.
device_types_folder = ./data/DeviceTypes/
device_types_scripts_folder = ./data/DeviceTypes/Scripts/
[storageadapter]
webservice_url = "${PCS_STORAGEADAPTER_WEBSERVICE_URL}"
webservice_timeout = 10000

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

@ -31,9 +31,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
private const string IoTHubConnStringKey = ApplicationKey + "iothub_connstring";
private const string CorsWhitelistKey = ApplicationKey + "cors_whitelist";
private const string IoTHubManagerKey = "iothubmanager:";
private const string IoTHubManagerApiUrlKey = IoTHubManagerKey + "webservice_url";
private const string IoTHubManagerApiTimeoutKey = IoTHubManagerKey + "webservice_timeout";
private const string StorageAdapterKey = "storageadapter:";
private const string StorageAdapterApiUrlKey = StorageAdapterKey + "webservice_url";
private const string StorageAdapterApiTimeoutKey = StorageAdapterKey + "webservice_timeout";
/// <summary>Web service listening port</summary>
public int Port { get; }
@ -71,7 +71,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
{
DeviceTypesFolder = MapRelativePath(configData.GetString(DeviceTypesFolderKey)),
DeviceTypesScriptsFolder = MapRelativePath(configData.GetString(DeviceTypesScriptsFolderKey)),
IoTHubConnString = connstring
IoTHubConnString = connstring,
StorageAdapterApiUrl = configData.GetString(StorageAdapterApiUrlKey),
StorageAdapterApiTimeout = configData.GetInt(StorageAdapterApiTimeoutKey)
};
}

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

@ -7,3 +7,7 @@ cors_whitelist = "{ 'origins': ['*'], 'methods': ['*'], 'headers': ['*'] }"
# which can be mounted to inject custom device types and scripts.
device_types_folder = ./data/DeviceTypes/
device_types_scripts_folder = ./data/DeviceTypes/Scripts/
[storageadapter]
webservice_url = "${PCS_STORAGEADAPTER_WEBSERVICE_URL}"
webservice_timeout = 10000

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

@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
@ -24,19 +25,19 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
}
[HttpGet]
public SimulationListApiModel Get()
public async Task<SimulationListApiModel> GetAsync()
{
return new SimulationListApiModel(this.simulationsService.GetList());
return new SimulationListApiModel(await this.simulationsService.GetListAsync());
}
[HttpGet("{id}")]
public SimulationApiModel Get(string id)
public async Task<SimulationApiModel> GetAsync(string id)
{
return new SimulationApiModel(this.simulationsService.Get(id));
return new SimulationApiModel(await this.simulationsService.GetAsync(id));
}
[HttpPost]
public SimulationApiModel Post(
public async Task<SimulationApiModel> PostAsync(
[FromBody] SimulationApiModel simulation,
[FromQuery(Name = "template")] string template = "")
{
@ -44,8 +45,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
{
if (string.IsNullOrEmpty(template))
{
this.log.Warn("No data or invalid data provided",
() => new { simulation, template });
this.log.Warn("No data or invalid data provided", () => new { simulation, template });
throw new BadRequestException("No data or invalid data provided.");
}
@ -53,33 +53,26 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
}
return new SimulationApiModel(
this.simulationsService.Insert(simulation.ToServiceModel(), template));
await this.simulationsService.InsertAsync(simulation.ToServiceModel(), template));
}
[HttpPut("{id}")]
public SimulationApiModel Put(
public async Task<SimulationApiModel> PutAsync(
[FromBody] SimulationApiModel simulation,
string id = "",
[FromQuery(Name = "template")] string template = "")
string id = "")
{
if (simulation == null)
{
if (string.IsNullOrEmpty(template))
{
this.log.Warn("No data or invalid data provided",
() => new { id, simulation, template });
throw new BadRequestException("No data or invalid data provided.");
}
simulation = new SimulationApiModel();
this.log.Warn("No data or invalid data provided", () => new { simulation });
throw new BadRequestException("No data or invalid data provided.");
}
return new SimulationApiModel(
this.simulationsService.Upsert(simulation.ToServiceModel(id), template));
await this.simulationsService.UpsertAsync(simulation.ToServiceModel(id)));
}
[HttpPatch("{id}")]
public SimulationApiModel Patch(
public async Task<SimulationApiModel> PatchAsync(
string id,
[FromBody] SimulationPatchApiModel patch)
{
@ -90,7 +83,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
}
return new SimulationApiModel(
this.simulationsService.Merge(patch.ToServiceModel(id)));
await this.simulationsService.MergeAsync(patch.ToServiceModel(id)));
}
}
}

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

@ -40,7 +40,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
statusMsg = "Unable to use Azure IoT Hub service";
}
var simulation = this.simulations.GetList().FirstOrDefault();
var simulation = (await this.simulations.GetListAsync()).FirstOrDefault();
var running = (simulation != null && simulation.Enabled);
var result = new StatusApiModel(statusIsOk, statusMsg);

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

@ -4,3 +4,8 @@ if [[ -z "$PCS_IOTHUB_CONNSTRING" ]]; then
echo "Error: the PCS_IOTHUB_CONNSTRING environment variable is not defined."
exit 1
fi
if [[ -z "$PCS_STORAGEADAPTER_WEBSERVICE_URL" ]]; then
echo "Error: the PCS_STORAGEADAPTER_WEBSERVICE_URL environment variable is not defined."
exit 1
fi

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

@ -5,4 +5,9 @@ IF "%PCS_IOTHUB_CONNSTRING%" == "" (
exit /B 1
)
IF "%PCS_STORAGEADAPTER_WEBSERVICE_URL%" == "" (
echo Error: the PCS_STORAGEADAPTER_WEBSERVICE_URL environment variable is not defined.
exit /B 1
)
endlocal

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

@ -10,3 +10,6 @@
# Azure IoT Hub Connection string
export PCS_IOTHUB_CONNSTRING="..."
# Endpoint to reach the storage adapter
export PCS_STORAGEADAPTER_WEBSERVICE_URL="http://127.0.0.1:9022/v1"

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

@ -8,3 +8,6 @@
:: Azure IoT Hub Connection string
SETX PCS_IOTHUB_CONNSTRING "..."
:: Endpoint to reach the storage adapter
SETX PCS_STORAGEADAPTER_WEBSERVICE_URL "http://127.0.0.1:9022/v1"

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

@ -6,6 +6,7 @@
# Run the service inside a Docker container: ./scripts/run --in-sandbox
# Run only the web service: ./scripts/run --webservice
# Run only the simulation: ./scripts/run --simulation
# Run only the simulation: ./scripts/run --storageadapter
# Show how to use this script: ./scripts/run -h
# Show how to use this script: ./scripts/run --help
@ -24,10 +25,11 @@ PCS_CACHE="/tmp/azure/iotpcs/.cache"
help() {
echo "Usage:"
echo " Run the service in the local environment: ./scripts/run"
echo " Run the service inside a Docker container: ./scripts/run -s|--in-sandbox"
echo " Run the service inside a Docker container: ./scripts/run -s | --in-sandbox"
echo " Run only the web service: ./scripts/run --webservice"
echo " Run only the simulation: ./scripts/run --simulation"
echo " Show how to use this script: ./scripts/run -h|--help"
echo " Run the storage adapter dependency: ./scripts/run --storageadapter"
echo " Show how to use this script: ./scripts/run -h | --help"
}
setup_sandbox_cache() {
@ -91,10 +93,17 @@ run_webservice() {
}
run_simulation() {
echo "Starting simulation..."
echo "Starting simulation agent..."
dotnet run --configuration $CONFIGURATION --project SimulationAgent/*.csproj
}
run_storageadapter() {
echo "Starting storage adapter..."
docker run -it -p 9022:9022 \
-e "PCS_STORAGEADAPTER_DOCUMENTDB_CONNSTRING=$PCS_STORAGEADAPTER_DOCUMENTDB_CONNSTRING" \
azureiotpcs/pcs-storage-adapter-dotnet
}
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
help
elif [[ "$1" == "--in-sandbox" || "$1" == "-s" ]]; then
@ -108,6 +117,8 @@ elif [[ "$1" == "--webservice" ]]; then
elif [[ "$1" == "--simulation" ]]; then
prepare_for_run
run_simulation
elif [[ "$1" == "--storageadapter" ]]; then
run_storageadapter
fi
set +e

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

@ -6,6 +6,7 @@
:: Run the service inside a Docker container: scripts\run --in-sandbox
:: Run only the web service: scripts\run --webservice
:: Run only the simulation: scripts\run --simulation
:: Run only the simulation: scripts\run --storageadapter
:: Show how to use this script: scripts\run -h
:: Show how to use this script: scripts\run --help
@ -23,15 +24,17 @@ IF "%1"=="-s" GOTO :RunInSandbox
IF "%1"=="--in-sandbox" GOTO :RunInSandbox
IF "%1"=="--webservice" GOTO :RunWebService
IF "%1"=="--simulation" GOTO :RunSimulation
IF "%1"=="--storageadapter" GOTO :RunStorageAdapter
:Help
echo "Usage:"
echo " Run the service in the local environment: ./scripts/run"
echo " Run the service inside a Docker container: ./scripts/run -s|--in-sandbox"
echo " Run the service inside a Docker container: ./scripts/run -s | --in-sandbox"
echo " Run only the web service: ./scripts/run --webservice"
echo " Run only the simulation: ./scripts/run --simulation"
echo " Show how to use this script: ./scripts/run -h|--help"
echo " Run the storage adapter dependency: ./scripts/run --storageadapter"
echo " Show how to use this script: ./scripts/run -h | --help"
goto :END
@ -50,6 +53,7 @@ IF "%1"=="--simulation" GOTO :RunSimulation
call dotnet restore
IF %ERRORLEVEL% NEQ 0 GOTO FAIL
echo Starting simulation agent and web service...
start "" dotnet run --configuration %CONFIGURATION% --project SimulationAgent/SimulationAgent.csproj
IF %ERRORLEVEL% NEQ 0 GOTO FAIL
@ -73,6 +77,7 @@ IF "%1"=="--simulation" GOTO :RunSimulation
call dotnet restore
IF %ERRORLEVEL% NEQ 0 GOTO FAIL
echo Starting web service...
dotnet run --configuration %CONFIGURATION% --project WebService/WebService.csproj
IF %ERRORLEVEL% NEQ 0 GOTO FAIL
@ -93,6 +98,7 @@ IF "%1"=="--simulation" GOTO :RunSimulation
call dotnet restore
IF %ERRORLEVEL% NEQ 0 GOTO FAIL
echo Starting simulation agent...
dotnet run --configuration %CONFIGURATION% --project SimulationAgent/SimulationAgent.csproj
IF %ERRORLEVEL% NEQ 0 GOTO FAIL
@ -136,6 +142,20 @@ IF "%1"=="--simulation" GOTO :RunSimulation
goto :END
:RunStorageAdapter
echo Starting storage adapter...
docker run -it -p 9022:9022 ^
-e "PCS_STORAGEADAPTER_DOCUMENTDB_CONNSTRING=%PCS_STORAGEADAPTER_DOCUMENTDB_CONNSTRING%" ^
azureiotpcs/pcs-storage-adapter-dotnet
:: Error 125 typically triggers in Windows if the drive is not shared
IF %ERRORLEVEL% EQU 125 GOTO DOCKER_SHARE
IF %ERRORLEVEL% NEQ 0 GOTO FAIL
goto :END
:: - - - - - - - - - - - - - -

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

@ -1 +1 @@
0.1.4
0.1.5