From 6cb7e7e24ae095a5f1048eee84cd9122d0650963 Mon Sep 17 00:00:00 2001 From: Cheick Keita Date: Fri, 19 Aug 2022 11:57:19 -0700 Subject: [PATCH] Make EntityConverter singleton (#2267) * Make the EntityConverter a singleton * Make the EntityConverter a singleton * format * fix tests --- .../ApiService/Functions/AgentEvents.cs | 6 ++--- src/ApiService/ApiService/Program.cs | 1 + .../TestHooks/InstanceConfigTestHooks.cs | 2 +- .../ApiService/onefuzzlib/OnefuzzContext.cs | 6 ++++- .../ApiService/onefuzzlib/ProxyOperations.cs | 3 ++- .../onefuzzlib/WebhookOperations.cs | 1 - .../onefuzzlib/orm/EntityConverter.cs | 22 ++++++++++--------- .../ApiService/onefuzzlib/orm/Orm.cs | 2 +- .../IntegrationTests/Fakes/TestContext.cs | 8 +++---- .../IntegrationTests/_FunctionTestBase.cs | 2 +- src/ApiService/Tests/RequestsTests.cs | 4 ++-- 11 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/ApiService/ApiService/Functions/AgentEvents.cs b/src/ApiService/ApiService/Functions/AgentEvents.cs index 447bc7d742..912ca454eb 100644 --- a/src/ApiService/ApiService/Functions/AgentEvents.cs +++ b/src/ApiService/ApiService/Functions/AgentEvents.cs @@ -16,8 +16,6 @@ public AgentEvents(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext _context = context; } - private static readonly EntityConverter _entityConverter = new(); - [Function("AgentEvents")] public Async.Task Run( [HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route="agents/events")] @@ -31,7 +29,7 @@ private async Async.Task Post(HttpRequestData req) { } var envelope = request.OkV; - _log.Info($"node event: machine_id: {envelope.MachineId} event: {_entityConverter.ToJsonString(envelope)}"); + _log.Info($"node event: machine_id: {envelope.MachineId} event: {EntityConverter.ToJsonString(envelope)}"); var error = envelope.Event switch { NodeStateUpdate updateEvent => await OnStateUpdate(envelope.MachineId, updateEvent), @@ -145,7 +143,7 @@ private async Async.Task Post(HttpRequestData req) { Error? error = null; if (ev.Data is NodeDoneEventData doneData) { if (doneData.Error is not null) { - var errorText = _entityConverter.ToJsonString(doneData); + var errorText = EntityConverter.ToJsonString(doneData); error = new Error(ErrorCode.TASK_FAILED, Errors: new string[] { errorText }); _log.Error($"node 'done' with error: machine_id:{machineId}, data:{errorText}"); } diff --git a/src/ApiService/ApiService/Program.cs b/src/ApiService/ApiService/Program.cs index f7fd2c2df0..7975911a08 100644 --- a/src/ApiService/ApiService/Program.cs +++ b/src/ApiService/ApiService/Program.cs @@ -103,6 +103,7 @@ public async static Async.Task Main() { .AddScoped() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/src/ApiService/ApiService/TestHooks/InstanceConfigTestHooks.cs b/src/ApiService/ApiService/TestHooks/InstanceConfigTestHooks.cs index 95b9dc3b69..90e4c63723 100644 --- a/src/ApiService/ApiService/TestHooks/InstanceConfigTestHooks.cs +++ b/src/ApiService/ApiService/TestHooks/InstanceConfigTestHooks.cs @@ -31,7 +31,7 @@ public async Task Get([HttpTrigger(AuthorizationLevel.Anonymou await resp.WriteAsJsonAsync(err); return resp; } else { - var str = (new EntityConverter()).ToJsonString(config); + var str = EntityConverter.ToJsonString(config); var resp = req.CreateResponse(HttpStatusCode.OK); await resp.WriteStringAsync(str); diff --git a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs index 52f357ac9f..013e4b3bda 100644 --- a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs +++ b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs @@ -1,4 +1,6 @@ -namespace Microsoft.OneFuzz.Service; +using Microsoft.OneFuzz.Service.OneFuzzLib.Orm; + +namespace Microsoft.OneFuzz.Service; using Microsoft.Extensions.DependencyInjection; @@ -40,6 +42,7 @@ public interface IOnefuzzContext { INsgOperations NsgOperations { get; } ISubnet Subnet { get; } IImageOperations ImageOperations { get; } + EntityConverter EntityConverter { get; } } public class OnefuzzContext : IOnefuzzContext { @@ -85,4 +88,5 @@ public OnefuzzContext(IServiceProvider serviceProvider) { public INsgOperations NsgOperations => _serviceProvider.GetRequiredService(); public ISubnet Subnet => _serviceProvider.GetRequiredService(); public IImageOperations ImageOperations => _serviceProvider.GetRequiredService(); + public EntityConverter EntityConverter => _serviceProvider.GetRequiredService(); } diff --git a/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs b/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs index a5eba155f4..dbdc4d9c80 100644 --- a/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/ProxyOperations.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using ApiService.OneFuzzLib.Orm; using Azure.Storage.Sas; +using Microsoft.OneFuzz.Service.OneFuzzLib.Orm; namespace Microsoft.OneFuzz.Service; @@ -105,7 +106,7 @@ public async Async.Task SaveProxyConfig(Proxy proxy) { MicrosoftTelemetryKey: _context.ServiceConfiguration.OneFuzzTelemetry.EnsureNotNull("missing Telemetry"), InstanceId: await _context.Containers.GetInstanceId()); - await _context.Containers.SaveBlob(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", _entityConverter.ToJsonString(proxyConfig), StorageType.Config); + await _context.Containers.SaveBlob(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", EntityConverter.ToJsonString(proxyConfig), StorageType.Config); } diff --git a/src/ApiService/ApiService/onefuzzlib/WebhookOperations.cs b/src/ApiService/ApiService/onefuzzlib/WebhookOperations.cs index d610a39eaa..f539ef135b 100644 --- a/src/ApiService/ApiService/onefuzzlib/WebhookOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/WebhookOperations.cs @@ -86,7 +86,6 @@ public async Task Ping(Webhook webhook) { // Not converting to bytes, as it's not neccessary in C#. Just keeping as string. public async Async.Task> BuildMessage(Guid webhookId, Guid eventId, EventType eventType, BaseEvent webhookEvent, String? secretToken, WebhookMessageFormat? messageFormat) { - var entityConverter = new EntityConverter(); string data = ""; if (messageFormat != null && messageFormat == WebhookMessageFormat.EventGrid) { var eventGridMessage = new[] { new WebhookMessageEventGrid(Id: eventId, Data: webhookEvent, DataVersion: "1.0.0", Subject: _context.Creds.GetInstanceName(), EventType: eventType, EventTime: DateTimeOffset.UtcNow) }; diff --git a/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs b/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs index 89c5008875..f19cdeaaa0 100644 --- a/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs +++ b/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs @@ -88,22 +88,24 @@ public override string ConvertName(string name) { } } public class EntityConverter { - private readonly JsonSerializerOptions _options; private readonly ConcurrentDictionary _cache; + private static readonly JsonSerializerOptions _options; + + static EntityConverter() { + _options = new JsonSerializerOptions() { + PropertyNamingPolicy = new OnefuzzNamingPolicy(), + }; + _options.Converters.Add(new CustomEnumConverterFactory()); + _options.Converters.Add(new PolymorphicConverterFactory()); + } public EntityConverter() { - _options = GetJsonSerializerOptions(); _cache = new ConcurrentDictionary(); } public static JsonSerializerOptions GetJsonSerializerOptions() { - var options = new JsonSerializerOptions() { - PropertyNamingPolicy = new OnefuzzNamingPolicy(), - }; - options.Converters.Add(new CustomEnumConverterFactory()); - options.Converters.Add(new PolymorphicConverterFactory()); - return options; + return _options; } internal static Func BuildConstructerFrom(ConstructorInfo constructorInfo) { @@ -167,9 +169,9 @@ private EntityInfo GetEntityInfo() { }); } - public string ToJsonString(T typedEntity) => JsonSerializer.Serialize(typedEntity, _options); + public static string ToJsonString(T typedEntity) => JsonSerializer.Serialize(typedEntity, _options); - public T? FromJsonString(string value) => JsonSerializer.Deserialize(value, _options); + public static T? FromJsonString(string value) => JsonSerializer.Deserialize(value, _options); public TableEntity ToTableEntity(T typedEntity) where T : EntityBase { if (typedEntity == null) { diff --git a/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs b/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs index aa62a6ea04..078fdc414c 100644 --- a/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs +++ b/src/ApiService/ApiService/onefuzzlib/orm/Orm.cs @@ -38,7 +38,7 @@ public class Orm : IOrm where T : EntityBase { public Orm(ILogTracer logTracer, IOnefuzzContext context) { _context = context; _logTracer = logTracer; - _entityConverter = new EntityConverter(); + _entityConverter = _context.EntityConverter; } public async IAsyncEnumerable QueryAsync(string? filter = null) { diff --git a/src/ApiService/IntegrationTests/Fakes/TestContext.cs b/src/ApiService/IntegrationTests/Fakes/TestContext.cs index 9d527fecb6..1e36b74ce8 100644 --- a/src/ApiService/IntegrationTests/Fakes/TestContext.cs +++ b/src/ApiService/IntegrationTests/Fakes/TestContext.cs @@ -11,13 +11,12 @@ namespace IntegrationTests.Fakes; // of functions as unit or integration tests. public sealed class TestContext : IOnefuzzContext { public TestContext(ILogTracer logTracer, IStorage storage, ICreds creds, string storagePrefix) { + EntityConverter = new EntityConverter(); ServiceConfiguration = new TestServiceConfiguration(storagePrefix); - Storage = storage; Creds = creds; Containers = new Containers(logTracer, Storage, Creds, ServiceConfiguration); Queue = new Queue(Storage, logTracer); - RequestHandling = new RequestHandling(logTracer); TaskOperations = new TaskOperations(logTracer, this); NodeOperations = new NodeOperations(logTracer, this); @@ -28,9 +27,7 @@ public TestContext(ILogTracer logTracer, IStorage storage, ICreds creds, string ConfigOperations = new ConfigOperations(logTracer, this); PoolOperations = new PoolOperations(logTracer, this); ScalesetOperations = new ScalesetOperations(logTracer, this); - UserCredentials = new UserCredentials(logTracer, ConfigOperations); - } public TestEvents Events { get; set; } = new(); @@ -71,6 +68,7 @@ public Async.Task InsertAll(params EntityBase[] objs) public IConfigOperations ConfigOperations { get; } public IPoolOperations PoolOperations { get; } public IScalesetOperations ScalesetOperations { get; } + public EntityConverter EntityConverter { get; } // -- Remainder not implemented -- @@ -113,4 +111,6 @@ public Async.Task InsertAll(params EntityBase[] objs) public ISubnet Subnet => throw new NotImplementedException(); public IImageOperations ImageOperations => throw new NotImplementedException(); + + } diff --git a/src/ApiService/IntegrationTests/_FunctionTestBase.cs b/src/ApiService/IntegrationTests/_FunctionTestBase.cs index 9bc8fc33a1..c43f5895a4 100644 --- a/src/ApiService/IntegrationTests/_FunctionTestBase.cs +++ b/src/ApiService/IntegrationTests/_FunctionTestBase.cs @@ -59,7 +59,7 @@ protected static string BodyAsString(HttpResponseData data) { } protected static T BodyAs(HttpResponseData data) - => new EntityConverter().FromJsonString(BodyAsString(data)) ?? throw new Exception($"unable to deserialize body as {typeof(T)}"); + => EntityConverter.FromJsonString(BodyAsString(data)) ?? throw new Exception($"unable to deserialize body as {typeof(T)}"); public void Dispose() { GC.SuppressFinalize(this); diff --git a/src/ApiService/Tests/RequestsTests.cs b/src/ApiService/Tests/RequestsTests.cs index 3aa80d677c..b79c44e986 100644 --- a/src/ApiService/Tests/RequestsTests.cs +++ b/src/ApiService/Tests/RequestsTests.cs @@ -10,7 +10,7 @@ namespace Tests; // This class contains tests for serialization and -// deserialization of examples generated by the +// deserialization of examples generated by the // onefuzz-agent’s `debug` sub-command. We test each // example for roundtripping which ensures that no // data is lost upon deserialization. @@ -25,7 +25,7 @@ public class RequestsTests { private static JsonSerializerOptions serializationOptions() { // base on the serialization options used at runtime, but // also indent to match inputs: - var result = EntityConverter.GetJsonSerializerOptions(); + var result = new JsonSerializerOptions(EntityConverter.GetJsonSerializerOptions()); result.WriteIndented = true; return result; }