rework MareConfigurationServiceClient
This commit is contained in:
@@ -9,6 +9,11 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ../data/postgresql/:/var/lib/postgresql/data
|
- ../data/postgresql/:/var/lib/postgresql/data
|
||||||
- postgres_socket:/var/run/postgresql:rw
|
- postgres_socket:/var/run/postgresql:rw
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U mare"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
haproxy:
|
haproxy:
|
||||||
image: haproxy:latest
|
image: haproxy:latest
|
||||||
@@ -35,7 +40,8 @@ services:
|
|||||||
- ../log/server-shard-main/:/opt/MareSynchronosServer/logs/:rw
|
- ../log/server-shard-main/:/opt/MareSynchronosServer/logs/:rw
|
||||||
- postgres_socket:/var/run/postgresql/:rw
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
depends_on:
|
depends_on:
|
||||||
- "postgres"
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
mare-shard-1:
|
mare-shard-1:
|
||||||
image: darkarchon/mare-synchronos-server:latest
|
image: darkarchon/mare-synchronos-server:latest
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ../data/postgresql/:/var/lib/postgresql/data
|
- ../data/postgresql/:/var/lib/postgresql/data
|
||||||
- postgres_socket:/var/run/postgresql:rw
|
- postgres_socket:/var/run/postgresql:rw
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U mare"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
mare-server:
|
mare-server:
|
||||||
image: darkarchon/mare-synchronos-server:latest
|
image: darkarchon/mare-synchronos-server:latest
|
||||||
@@ -22,7 +27,8 @@ services:
|
|||||||
- ../log/server-standalone/:/opt/MareSynchronosServer/logs/:rw
|
- ../log/server-standalone/:/opt/MareSynchronosServer/logs/:rw
|
||||||
- postgres_socket:/var/run/postgresql/:rw
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
depends_on:
|
depends_on:
|
||||||
- "postgres"
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
mare-services:
|
mare-services:
|
||||||
image: darkarchon/mare-synchronos-services:latest
|
image: darkarchon/mare-synchronos-services:latest
|
||||||
@@ -34,7 +40,8 @@ services:
|
|||||||
- ../log/services-standalone/:/opt/MareSynchronosServices/logs/:rw
|
- ../log/services-standalone/:/opt/MareSynchronosServices/logs/:rw
|
||||||
- postgres_socket:/var/run/postgresql/:rw
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
depends_on:
|
depends_on:
|
||||||
- "postgres"
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
- "mare-server"
|
- "mare-server"
|
||||||
|
|
||||||
mare-files:
|
mare-files:
|
||||||
@@ -48,7 +55,8 @@ services:
|
|||||||
- postgres_socket:/var/run/postgresql/:rw
|
- postgres_socket:/var/run/postgresql/:rw
|
||||||
- ../data/files-standalone/:/marecache/:rw
|
- ../data/files-standalone/:/marecache/:rw
|
||||||
depends_on:
|
depends_on:
|
||||||
- "postgres"
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
- "mare-server"
|
- "mare-server"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ public class Program
|
|||||||
using var context = services.GetRequiredService<MareDbContext>();
|
using var context = services.GetRequiredService<MareDbContext>();
|
||||||
var options = services.GetRequiredService<IConfigurationService<ServerConfiguration>>();
|
var options = services.GetRequiredService<IConfigurationService<ServerConfiguration>>();
|
||||||
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
||||||
logger.LogInformation("Loaded MareSynchronos Server Configuration (IsMain: {isMain})", options.IsMain);
|
|
||||||
logger.LogInformation(options.ToString());
|
|
||||||
|
|
||||||
if (options.IsMain)
|
if (options.IsMain)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -233,6 +233,8 @@ public class Startup
|
|||||||
c.GetService<IOptions<MareConfigurationAuthBase>>(),
|
c.GetService<IOptions<MareConfigurationAuthBase>>(),
|
||||||
c.GetService<GrpcClientFactory>(),
|
c.GetService<GrpcClientFactory>(),
|
||||||
"MainServer"));
|
"MainServer"));
|
||||||
|
services.AddHostedService(p => (MareConfigurationServiceClient<ServerConfiguration>)p.GetService<IConfigurationService<ServerConfiguration>>());
|
||||||
|
services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ public class Startup
|
|||||||
c.GetService<IOptions<MareConfigurationAuthBase>>(),
|
c.GetService<IOptions<MareConfigurationAuthBase>>(),
|
||||||
c.GetService<GrpcClientFactory>(),
|
c.GetService<GrpcClientFactory>(),
|
||||||
"MainServer"));
|
"MainServer"));
|
||||||
|
|
||||||
|
services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>());
|
||||||
|
services.AddHostedService(p => (MareConfigurationServiceClient<ServerConfiguration>)p.GetService<IConfigurationService<ServerConfiguration>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
using Grpc.Net.ClientFactory;
|
using Grpc.Net.ClientFactory;
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
using MareSynchronosShared.Utils;
|
using MareSynchronosShared.Utils;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using static MareSynchronosShared.Protos.ConfigurationService;
|
using static MareSynchronosShared.Protos.ConfigurationService;
|
||||||
|
|
||||||
namespace MareSynchronosShared.Services;
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
public class MareConfigurationServiceClient<T> : IConfigurationService<T> where T : class, IMareConfiguration
|
public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationService<T> where T : class, IMareConfiguration
|
||||||
{
|
{
|
||||||
internal record RemoteCachedEntry(object Value, DateTime Inserted);
|
internal record RemoteCachedEntry(object Value, DateTime Inserted);
|
||||||
|
|
||||||
private readonly T _config;
|
private readonly T _config;
|
||||||
private readonly ConcurrentDictionary<string, RemoteCachedEntry> _cachedRemoteProperties = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, object> _cachedRemoteProperties = new(StringComparer.Ordinal);
|
||||||
private readonly ILogger<MareConfigurationServiceClient<T>> _logger;
|
private readonly ILogger<MareConfigurationServiceClient<T>> _logger;
|
||||||
private readonly GrpcClientFactory _grpcClientFactory;
|
private readonly GrpcClientFactory _grpcClientFactory;
|
||||||
private readonly string _grpcClientName;
|
private readonly string _grpcClientName;
|
||||||
|
private readonly CancellationTokenSource _updateTaskCts = new();
|
||||||
|
private ConfigurationServiceClient _configurationServiceClient;
|
||||||
|
private bool _initialized = false;
|
||||||
|
|
||||||
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptions<T> config, GrpcClientFactory grpcClientFactory, string grpcClientName)
|
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptions<T> config, GrpcClientFactory grpcClientFactory, string grpcClientName)
|
||||||
{
|
{
|
||||||
@@ -37,90 +42,26 @@ public class MareConfigurationServiceClient<T> : IConfigurationService<T> where
|
|||||||
if (prop == null) return defaultValue;
|
if (prop == null) return defaultValue;
|
||||||
if (prop.PropertyType != typeof(T1)) throw new InvalidCastException($"Invalid Cast: Property {key} is {prop.PropertyType}, wanted: {typeof(T1)}");
|
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();
|
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)remotevalue;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = prop.GetValue(_config);
|
var value = prop.GetValue(_config);
|
||||||
|
var defaultPropValue = prop.PropertyType.IsValueType ? Activator.CreateInstance(prop.PropertyType) : null;
|
||||||
|
if (value == defaultPropValue) return defaultValue;
|
||||||
return (T1)value;
|
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<ConfigurationServiceClient>(_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<T1>(string key)
|
public T1 GetValue<T1>(string key)
|
||||||
{
|
{
|
||||||
var prop = _config.GetType().GetProperty(key);
|
var prop = _config.GetType().GetProperty(key);
|
||||||
if (prop == null) throw new KeyNotFoundException(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)}");
|
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();
|
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)remotevalue;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = prop.GetValue(_config);
|
var value = prop.GetValue(_config);
|
||||||
@@ -141,4 +82,84 @@ public class MareConfigurationServiceClient<T> : IConfigurationService<T> where
|
|||||||
}
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<T1> GetValueFromGrpc<T1>(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<T1>(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<ConfigurationServiceClient>(_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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -124,6 +124,8 @@ public class Startup
|
|||||||
p.GetRequiredService<IOptions<StaticFilesServerConfiguration>>(),
|
p.GetRequiredService<IOptions<StaticFilesServerConfiguration>>(),
|
||||||
p.GetRequiredService<GrpcClientFactory>(),
|
p.GetRequiredService<GrpcClientFactory>(),
|
||||||
"FileServer"));
|
"FileServer"));
|
||||||
|
|
||||||
|
services.AddHostedService(p => (MareConfigurationServiceClient<StaticFilesServerConfiguration>)p.GetService<IConfigurationService<StaticFilesServerConfiguration>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>>(p =>
|
services.AddSingleton<IConfigurationService<MareConfigurationAuthBase>>(p =>
|
||||||
@@ -132,6 +134,8 @@ public class Startup
|
|||||||
p.GetService<IOptions<MareConfigurationAuthBase>>(),
|
p.GetService<IOptions<MareConfigurationAuthBase>>(),
|
||||||
p.GetRequiredService<GrpcClientFactory>(), "MainServer")
|
p.GetRequiredService<GrpcClientFactory>(), "MainServer")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
|||||||
Reference in New Issue
Block a user