From de2defe69263d287952182eb3cec53e1a4f1603c Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Wed, 28 Dec 2022 04:24:53 +0100 Subject: [PATCH] rework MareConfigurationServiceClient --- Docker/run/compose/mare-sharded.yml | 8 +- Docker/run/compose/mare-standalone.yml | 14 +- .../MareSynchronosServer/Program.cs | 2 - .../MareSynchronosServer/Startup.cs | 2 + .../MareSynchronosServices/Startup.cs | 3 + .../MareConfigurationServiceClient.cs | 165 ++++++++++-------- .../Startup.cs | 4 + 7 files changed, 120 insertions(+), 78 deletions(-) diff --git a/Docker/run/compose/mare-sharded.yml b/Docker/run/compose/mare-sharded.yml index f9dc5c7..4d2954b 100644 --- a/Docker/run/compose/mare-sharded.yml +++ b/Docker/run/compose/mare-sharded.yml @@ -9,6 +9,11 @@ services: volumes: - ../data/postgresql/:/var/lib/postgresql/data - postgres_socket:/var/run/postgresql:rw + healthcheck: + test: ["CMD-SHELL", "pg_isready -U mare"] + interval: 5s + timeout: 5s + retries: 5 haproxy: image: haproxy:latest @@ -35,7 +40,8 @@ services: - ../log/server-shard-main/:/opt/MareSynchronosServer/logs/:rw - postgres_socket:/var/run/postgresql/:rw depends_on: - - "postgres" + postgres: + condition: service_healthy mare-shard-1: image: darkarchon/mare-synchronos-server:latest diff --git a/Docker/run/compose/mare-standalone.yml b/Docker/run/compose/mare-standalone.yml index 0c6dafd..b3914ac 100644 --- a/Docker/run/compose/mare-standalone.yml +++ b/Docker/run/compose/mare-standalone.yml @@ -9,6 +9,11 @@ services: volumes: - ../data/postgresql/:/var/lib/postgresql/data - postgres_socket:/var/run/postgresql:rw + healthcheck: + test: ["CMD-SHELL", "pg_isready -U mare"] + interval: 5s + timeout: 5s + retries: 5 mare-server: image: darkarchon/mare-synchronos-server:latest @@ -22,7 +27,8 @@ services: - ../log/server-standalone/:/opt/MareSynchronosServer/logs/:rw - postgres_socket:/var/run/postgresql/:rw depends_on: - - "postgres" + postgres: + condition: service_healthy mare-services: image: darkarchon/mare-synchronos-services:latest @@ -34,7 +40,8 @@ services: - ../log/services-standalone/:/opt/MareSynchronosServices/logs/:rw - postgres_socket:/var/run/postgresql/:rw depends_on: - - "postgres" + postgres: + condition: service_healthy - "mare-server" mare-files: @@ -48,7 +55,8 @@ services: - postgres_socket:/var/run/postgresql/:rw - ../data/files-standalone/:/marecache/:rw depends_on: - - "postgres" + postgres: + condition: service_healthy - "mare-server" volumes: diff --git a/MareSynchronosServer/MareSynchronosServer/Program.cs b/MareSynchronosServer/MareSynchronosServer/Program.cs index 6e5db64..dfe857b 100644 --- a/MareSynchronosServer/MareSynchronosServer/Program.cs +++ b/MareSynchronosServer/MareSynchronosServer/Program.cs @@ -18,8 +18,6 @@ public class Program using var context = services.GetRequiredService(); var options = services.GetRequiredService>(); var logger = host.Services.GetRequiredService>(); - logger.LogInformation("Loaded MareSynchronos Server Configuration (IsMain: {isMain})", options.IsMain); - logger.LogInformation(options.ToString()); if (options.IsMain) { diff --git a/MareSynchronosServer/MareSynchronosServer/Startup.cs b/MareSynchronosServer/MareSynchronosServer/Startup.cs index 090f783..bfe6c28 100644 --- a/MareSynchronosServer/MareSynchronosServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServer/Startup.cs @@ -233,6 +233,8 @@ public class Startup c.GetService>(), c.GetService(), "MainServer")); + services.AddHostedService(p => (MareConfigurationServiceClient)p.GetService>()); + services.AddHostedService(p => (MareConfigurationServiceClient)p.GetService>()); } else { diff --git a/MareSynchronosServer/MareSynchronosServices/Startup.cs b/MareSynchronosServer/MareSynchronosServices/Startup.cs index 1fce88a..1ecd417 100644 --- a/MareSynchronosServer/MareSynchronosServices/Startup.cs +++ b/MareSynchronosServer/MareSynchronosServices/Startup.cs @@ -85,6 +85,9 @@ public class Startup c.GetService>(), c.GetService(), "MainServer")); + + services.AddHostedService(p => (MareConfigurationServiceClient)p.GetService>()); + services.AddHostedService(p => (MareConfigurationServiceClient)p.GetService>()); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/MareSynchronosServer/MareSynchronosShared/Services/MareConfigurationServiceClient.cs b/MareSynchronosServer/MareSynchronosShared/Services/MareConfigurationServiceClient.cs index 0f858e2..cd00b64 100644 --- a/MareSynchronosServer/MareSynchronosShared/Services/MareConfigurationServiceClient.cs +++ b/MareSynchronosServer/MareSynchronosShared/Services/MareConfigurationServiceClient.cs @@ -1,25 +1,30 @@ using Grpc.Net.ClientFactory; using MareSynchronosShared.Protos; using MareSynchronosShared.Utils; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Concurrent; using System.Globalization; +using System.Reflection; using System.Text; using System.Text.Json; using static MareSynchronosShared.Protos.ConfigurationService; namespace MareSynchronosShared.Services; -public class MareConfigurationServiceClient : IConfigurationService where T : class, IMareConfiguration +public class MareConfigurationServiceClient : IHostedService, IConfigurationService where T : class, IMareConfiguration { internal record RemoteCachedEntry(object Value, DateTime Inserted); private readonly T _config; - private readonly ConcurrentDictionary _cachedRemoteProperties = new(StringComparer.Ordinal); + private readonly ConcurrentDictionary _cachedRemoteProperties = new(StringComparer.Ordinal); private readonly ILogger> _logger; private readonly GrpcClientFactory _grpcClientFactory; private readonly string _grpcClientName; + private readonly CancellationTokenSource _updateTaskCts = new(); + private ConfigurationServiceClient _configurationServiceClient; + private bool _initialized = false; public MareConfigurationServiceClient(ILogger> logger, IOptions config, GrpcClientFactory grpcClientFactory, string grpcClientName) { @@ -37,90 +42,26 @@ public class MareConfigurationServiceClient : IConfigurationService where if (prop == null) return defaultValue; if (prop.PropertyType != typeof(T1)) throw new InvalidCastException($"Invalid Cast: Property {key} is {prop.PropertyType}, wanted: {typeof(T1)}"); bool isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), inherit: true).Any(); - if (isRemote) + if (isRemote && _cachedRemoteProperties.TryGetValue(key, out var remotevalue)) { - if (_cachedRemoteProperties.TryGetValue(key, out var existingEntry)) - { - return (T1)_cachedRemoteProperties[key].Value; - } - - try - { - var result = GetValueFromGrpc(key, defaultValue, prop.PropertyType); - if (result == null) return defaultValue; - _cachedRemoteProperties[key] = result; - return (T1)_cachedRemoteProperties[key].Value; - } - catch (Exception ex) - { - if (existingEntry != null) - { - _logger.LogWarning(ex, "Could not get value for {key} from Grpc, returning existing", key); - return (T1)existingEntry.Value; - } - else - { - _logger.LogWarning(ex, "Could not get value for {key} from Grpc, returning default", key); - return defaultValue; - } - } + return (T1)remotevalue; } var value = prop.GetValue(_config); + var defaultPropValue = prop.PropertyType.IsValueType ? Activator.CreateInstance(prop.PropertyType) : null; + if (value == defaultPropValue) return defaultValue; return (T1)value; } - private RemoteCachedEntry? GetValueFromGrpc(string key, object defaultValue, Type t) - { - // grab stuff from grpc - try - { - _logger.LogInformation("Getting {key} from Grpc", key); - var configClient = _grpcClientFactory.CreateClient(_grpcClientName); - var response = configClient.GetConfigurationEntry(new KeyMessage { Key = key, Default = Convert.ToString(defaultValue, CultureInfo.InvariantCulture) }); - _logger.LogInformation("Grpc Response for {key} = {value}", key, response.Value); - return new RemoteCachedEntry(JsonSerializer.Deserialize(response.Value, t), DateTime.Now); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failure Getting Cached Entry"); - return null; - } - } - public T1 GetValue(string key) { var prop = _config.GetType().GetProperty(key); if (prop == null) throw new KeyNotFoundException(key); if (prop.PropertyType != typeof(T1)) throw new InvalidCastException($"Invalid Cast: Property {key} is {prop.PropertyType}, wanted: {typeof(T1)}"); bool isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), inherit: true).Any(); - if (isRemote) + if (isRemote && _cachedRemoteProperties.TryGetValue(key, out var remotevalue)) { - if (_cachedRemoteProperties.TryGetValue(key, out var existingEntry)) - { - return (T1)_cachedRemoteProperties[key].Value; - } - - try - { - var result = GetValueFromGrpc(key, null, prop.PropertyType); - if (result == null) throw new KeyNotFoundException(key); - _cachedRemoteProperties[key] = result; - return (T1)_cachedRemoteProperties[key].Value; - } - catch (Exception ex) - { - if (existingEntry != null) - { - _logger.LogWarning(ex, "Could not get value for {key} from Grpc, returning existing", key); - return (T1)existingEntry.Value; - } - else - { - _logger.LogWarning(ex, "Could not get value for {key} from Grpc, throwing exception", key); - throw new KeyNotFoundException(key); - } - } + return (T1)remotevalue; } var value = prop.GetValue(_config); @@ -141,4 +82,84 @@ public class MareConfigurationServiceClient : IConfigurationService where } return sb.ToString(); } + + private async Task GetValueFromGrpc(ConfigurationServiceClient client, string key, object defaultValue) + { + // grab stuff from grpc + try + { + _logger.LogInformation("Getting {key} from Grpc", key); + var response = await client.GetConfigurationEntryAsync(new KeyMessage { Key = key, Default = Convert.ToString(defaultValue, CultureInfo.InvariantCulture) }).ConfigureAwait(false); + _logger.LogInformation("Grpc Response for {key} = {value}", key, response.Value); + return JsonSerializer.Deserialize(response.Value); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failure Getting Remote Entry for {key}", key); + return (T1)defaultValue; + } + } + + private async Task UpdateRemoteProperties(CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + _logger.LogInformation("Getting Properties from GRPC"); + try + { + _configurationServiceClient = _grpcClientFactory.CreateClient(_grpcClientName); + var properties = _config.GetType().GetProperties(); + foreach (var prop in properties) + { + _logger.LogInformation("Checking Property " + prop.Name); + try + { + if (!prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()) continue; + var mi = GetType().GetMethod(nameof(GetValueFromGrpc), BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(prop.PropertyType); + var defaultValue = prop.PropertyType.IsValueType ? Activator.CreateInstance(prop.PropertyType) : null; + var task = (Task)mi.Invoke(this, new[] { _configurationServiceClient, prop.Name, defaultValue }); + await task.ConfigureAwait(false); + + var resultProperty = task.GetType().GetProperty("Result"); + var resultValue = resultProperty.GetValue(task); + + if (resultValue != defaultValue) + { + _cachedRemoteProperties[prop.Name] = resultValue; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during getting property " + prop.Name); + } + } + + if (!_initialized) + { + _initialized = true; + _logger.LogInformation("Finished initial getting properties from GRPC"); + _logger.LogInformation(ToString()); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Failure getting or updating properties from GRPC, retrying in 30min"); + } + + await Task.Delay(TimeSpan.FromMinutes(30), ct).ConfigureAwait(false); + } + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Starting MareConfigurationServiceClient"); + _ = UpdateRemoteProperties(_updateTaskCts.Token); + while (!_initialized && !cancellationToken.IsCancellationRequested) await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _updateTaskCts.Cancel(); + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs b/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs index 4b655e2..24e2738 100644 --- a/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs +++ b/MareSynchronosServer/MareSynchronosStaticFilesServer/Startup.cs @@ -124,6 +124,8 @@ public class Startup p.GetRequiredService>(), p.GetRequiredService(), "FileServer")); + + services.AddHostedService(p => (MareConfigurationServiceClient)p.GetService>()); } services.AddSingleton>(p => @@ -132,6 +134,8 @@ public class Startup p.GetService>(), p.GetRequiredService(), "MainServer") ); + + services.AddHostedService(p => (MareConfigurationServiceClient)p.GetService>()); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env)