- reload configs on change

- remove grpc config service replace with http
- add messaging to channel
This commit is contained in:
rootdarkarchon
2023-02-25 12:20:59 +01:00
parent 3490c5c8d8
commit aa03d400d4
38 changed files with 304 additions and 377 deletions

View File

@@ -1,132 +0,0 @@
using Grpc.Core;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace MareSynchronosShared.Services;
public abstract class GrpcBaseService : IHostedService, IDisposable
{
protected GrpcBaseService(ILogger logger)
{
_logger = logger;
}
private CancellationTokenSource _faultCheckCts = new();
private CancellationTokenSource _streamCts = new();
private readonly ILogger _logger;
protected bool GrpcIsFaulty { get; private set; }
protected abstract Task StartAsyncInternal(CancellationToken cancellationToken);
protected abstract Task StopAsyncInternal(CancellationToken cancellationToken);
protected abstract Task OnGrpcRestore();
protected abstract Task PreStartStream();
protected abstract Task StartStream(CancellationToken ct);
protected abstract Task PostStartStream();
public async Task StartAsync(CancellationToken cancellationToken)
{
_ = RestartStreams();
_ = CheckGrpcFaults(_faultCheckCts.Token);
await StartAsyncInternal(cancellationToken).ConfigureAwait(false);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_faultCheckCts.Cancel();
_streamCts.Cancel();
await StopAsyncInternal(cancellationToken).ConfigureAwait(false);
}
private async Task RestartStreams()
{
if (!GrpcIsFaulty)
{
_streamCts?.Cancel();
_streamCts?.Dispose();
_streamCts = new();
var token = _streamCts.Token;
try
{
await PreStartStream().ConfigureAwait(false);
await StartStream(token).ConfigureAwait(false);
await PostStartStream().ConfigureAwait(false);
}
catch
{
SetGrpcFaulty();
}
}
}
protected void SetGrpcFaulty()
{
if (!GrpcIsFaulty)
{
GrpcIsFaulty = true;
_logger.LogWarning("GRPC connection is faulty");
}
}
private async Task CheckGrpcFaults(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await CheckFaultStateAndRestore().ConfigureAwait(false);
}
catch
{
SetGrpcFaulty();
await Task.Delay(5000, ct).ConfigureAwait(false);
}
await Task.Delay(250).ConfigureAwait(false);
}
}
private async Task CheckFaultStateAndRestore()
{
if (GrpcIsFaulty)
{
GrpcIsFaulty = false;
await RestartStreams().ConfigureAwait(false);
await OnGrpcRestore().ConfigureAwait(false);
_logger.LogInformation("GRPC connection is restored");
}
}
protected async Task<T> InvokeOnGrpc<T>(AsyncUnaryCall<T> toExecute)
{
try
{
var result = await toExecute.ConfigureAwait(false);
return result;
}
catch
{
SetGrpcFaulty();
return default;
}
}
protected async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
{
try
{
await toExecute.ConfigureAwait(false);
await CheckFaultStateAndRestore().ConfigureAwait(false);
}
catch
{
SetGrpcFaulty();
}
}
public void Dispose()
{
_streamCts?.Dispose();
_faultCheckCts?.Dispose();
}
}

View File

@@ -1,30 +0,0 @@
using Grpc.Core;
using MareSynchronosShared.Protos;
using MareSynchronosShared.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace MareSynchronosShared.Services;
[Authorize]
[AllowAnonymous]
public class GrpcConfigurationService<T> : ConfigurationService.ConfigurationServiceBase where T : class, IMareConfiguration
{
private readonly T _config;
private readonly ILogger<GrpcConfigurationService<T>> logger;
public GrpcConfigurationService(IOptions<T> config, ILogger<GrpcConfigurationService<T>> logger)
{
_config = config.Value;
this.logger = logger;
}
[AllowAnonymous]
public override Task<ValueMessage> GetConfigurationEntry(KeyMessage request, ServerCallContext context)
{
logger.LogInformation("Remote requested {key}", request.Key);
var returnVal = _config.SerializeValue(request.Key, request.Default);
return Task.FromResult(new ValueMessage() { Value = returnVal });
}
}

View File

@@ -0,0 +1,61 @@
using MareSynchronosShared.Utils;
using MareSynchronosStaticFilesServer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace MareSynchronosShared.Services;
[Route("configuration/[controller]")]
[Authorize(Policy = "Internal")]
public class MareConfigurationController<T> : Controller where T : class, IMareConfiguration
{
private readonly ILogger<MareConfigurationController<T>> _logger;
private IOptionsMonitor<T> _config;
public MareConfigurationController(IOptionsMonitor<T> config, ILogger<MareConfigurationController<T>> logger)
{
_config = config;
_logger = logger;
}
[HttpGet("GetConfigurationEntry")]
[Authorize(Policy = "Internal")]
public IActionResult GetConfigurationEntry(string key, string defaultValue)
{
var result = _config.CurrentValue.SerializeValue(key, defaultValue);
_logger.LogInformation("Requested " + key + ", returning:" + result);
return Ok(result);
}
}
#pragma warning disable MA0048 // File name must match type name
public class MareStaticFilesServerConfigurationController : MareConfigurationController<StaticFilesServerConfiguration>
{
public MareStaticFilesServerConfigurationController(IOptionsMonitor<StaticFilesServerConfiguration> config, ILogger<MareStaticFilesServerConfigurationController> logger) : base(config, logger)
{
}
}
public class MareAuthBaseConfigurationController : MareConfigurationController<MareConfigurationAuthBase>
{
public MareAuthBaseConfigurationController(IOptionsMonitor<MareConfigurationAuthBase> config, ILogger<MareAuthBaseConfigurationController> logger) : base(config, logger)
{
}
}
public class MareServerConfigurationController : MareConfigurationController<ServerConfiguration>
{
public MareServerConfigurationController(IOptionsMonitor<ServerConfiguration> config, ILogger<MareServerConfigurationController> logger) : base(config, logger)
{
}
}
public class MareServicesConfigurationController : MareConfigurationController<ServicesConfiguration>
{
public MareServicesConfigurationController(IOptionsMonitor<ServicesConfiguration> config, ILogger<MareServicesConfigurationController> logger) : base(config, logger)
{
}
}
#pragma warning restore MA0048 // File name must match type name

View File

@@ -1,43 +1,55 @@
using Grpc.Net.ClientFactory;
using MareSynchronosShared.Protos;
using MareSynchronosShared.Utils;
using MareSynchronosShared.Utils;
using MareSynchronosStaticFilesServer;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections;
using System.Collections.Concurrent;
using System.Globalization;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Text.Json;
using static MareSynchronosShared.Protos.ConfigurationService;
namespace MareSynchronosShared.Services;
public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationService<T> where T : class, IMareConfiguration
{
private readonly T _config;
private readonly IOptionsMonitor<T> _config;
private readonly ConcurrentDictionary<string, object> _cachedRemoteProperties = new(StringComparer.Ordinal);
private readonly ILogger<MareConfigurationServiceClient<T>> _logger;
private readonly GrpcClientFactory _grpcClientFactory;
private readonly string _grpcClientName;
private readonly ServerTokenGenerator _serverTokenGenerator;
private readonly CancellationTokenSource _updateTaskCts = new();
private ConfigurationServiceClient _configurationServiceClient;
private bool _initialized = false;
private readonly HttpClient _httpClient;
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptions<T> config, GrpcClientFactory grpcClientFactory, string grpcClientName)
private Uri GetRoute(string key, string value)
{
_config = config.Value;
if (_config.CurrentValue.GetType() == typeof(ServerConfiguration))
return new Uri((_config.CurrentValue as ServerConfiguration).MainServerAddress, $"configuration/MareServerConfiguration/{nameof(MareServerConfigurationController.GetConfigurationEntry)}?key={key}&defaultValue={value}");
if (_config.CurrentValue.GetType() == typeof(MareConfigurationAuthBase))
return new Uri((_config.CurrentValue as MareConfigurationAuthBase).MainServerAddress, $"configuration/MareAuthBaseConfiguration/{nameof(MareAuthBaseConfigurationController.GetConfigurationEntry)}?key={key}&defaultValue={value}");
if (_config.CurrentValue.GetType() == typeof(ServicesConfiguration))
return new Uri((_config.CurrentValue as ServicesConfiguration).MainServerAddress, $"configuration/MareServicesConfiguration/{nameof(MareServicesConfigurationController.GetConfigurationEntry)}?key={key}&defaultValue={value}");
if (_config.CurrentValue.GetType() == typeof(StaticFilesServerConfiguration))
return new Uri((_config.CurrentValue as StaticFilesServerConfiguration).MainFileServerAddress, $"configuration/MareStaticFilesServerConfiguration/{nameof(MareStaticFilesServerConfigurationController.GetConfigurationEntry)}?key={key}&defaultValue={value}");
throw new NotSupportedException("Config is not supported to be gotten remotely");
}
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptionsMonitor<T> config, ServerTokenGenerator serverTokenGenerator)
{
_config = config;
_logger = logger;
_grpcClientFactory = grpcClientFactory;
_grpcClientName = grpcClientName;
_serverTokenGenerator = serverTokenGenerator;
_httpClient = new();
}
public bool IsMain => false;
public T1 GetValueOrDefault<T1>(string key, T1 defaultValue)
{
var prop = _config.GetType().GetProperty(key);
var prop = _config.CurrentValue.GetType().GetProperty(key);
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();
@@ -46,7 +58,7 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
return (T1)remotevalue;
}
var value = prop.GetValue(_config);
var value = prop.GetValue(_config.CurrentValue);
var defaultPropValue = prop.PropertyType.IsValueType ? Activator.CreateInstance(prop.PropertyType) : null;
if (value == defaultPropValue) return defaultValue;
return (T1)value;
@@ -54,7 +66,7 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
public T1 GetValue<T1>(string key)
{
var prop = _config.GetType().GetProperty(key);
var prop = _config.CurrentValue.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();
@@ -63,19 +75,19 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
return (T1)remotevalue;
}
var value = prop.GetValue(_config);
var value = prop.GetValue(_config.CurrentValue);
return (T1)value;
}
public override string ToString()
{
var props = _config.GetType().GetProperties();
var props = _config.CurrentValue.GetType().GetProperties();
StringBuilder sb = new();
foreach (var prop in props)
{
var isRemote = prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any();
var getValueMethod = GetType().GetMethod(nameof(GetValue)).MakeGenericMethod(prop.PropertyType);
var value = isRemote ? getValueMethod.Invoke(this, new[] { prop.Name }) : prop.GetValue(_config);
var value = isRemote ? getValueMethod.Invoke(this, new[] { prop.Name }) : prop.GetValue(_config.CurrentValue);
if (typeof(IEnumerable).IsAssignableFrom(prop.PropertyType) && !typeof(string).IsAssignableFrom(prop.PropertyType))
{
var enumVal = (IEnumerable)value;
@@ -90,15 +102,18 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
return sb.ToString();
}
private async Task<T1> GetValueFromGrpc<T1>(ConfigurationServiceClient client, string key, object defaultValue)
private async Task<T1> GetValueFromRemote<T1>(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);
_logger.LogInformation("Getting {key} from Http", key);
HttpRequestMessage msg = new(HttpMethod.Get, GetRoute(key, Convert.ToString(defaultValue, CultureInfo.InvariantCulture)));
msg.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _serverTokenGenerator.Token);
var response = await _httpClient.SendAsync(msg).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
_logger.LogInformation("Http Response for {key} = {value}", key, content);
return JsonSerializer.Deserialize<T1>(content);
}
catch (Exception ex)
{
@@ -111,20 +126,19 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
{
while (!ct.IsCancellationRequested)
{
_logger.LogInformation("Getting Properties from GRPC");
_logger.LogInformation("Getting Properties from Remote for " + typeof(T));
try
{
_configurationServiceClient = _grpcClientFactory.CreateClient<ConfigurationServiceClient>(_grpcClientName);
var properties = _config.GetType().GetProperties();
var properties = _config.CurrentValue.GetType().GetProperties();
foreach (var prop in properties)
{
try
{
if (!prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()) continue;
_logger.LogInformation("Checking Property " + prop.Name);
var mi = GetType().GetMethod(nameof(GetValueFromGrpc), BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(prop.PropertyType);
var mi = GetType().GetMethod(nameof(GetValueFromRemote), 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 });
var task = (Task)mi.Invoke(this, new[] { prop.Name, defaultValue });
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
@@ -147,12 +161,12 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
_initialized = true;
}
_logger.LogInformation("Saved properties from GRPC are now:");
_logger.LogInformation("Saved properties from HTTP are now:");
_logger.LogInformation(ToString());
}
catch (Exception ex)
{
_logger.LogError(ex, "Failure getting or updating properties from GRPC, retrying in 30min");
_logger.LogError(ex, "Failure getting or updating properties from HTTP, retrying in 30min");
}
await Task.Delay(TimeSpan.FromMinutes(30), ct).ConfigureAwait(false);
@@ -169,6 +183,7 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
public Task StopAsync(CancellationToken cancellationToken)
{
_updateTaskCts.Cancel();
_httpClient.Dispose();
return Task.CompletedTask;
}
}

View File

@@ -6,31 +6,31 @@ namespace MareSynchronosShared.Services;
public class MareConfigurationServiceServer<T> : IConfigurationService<T> where T : class, IMareConfiguration
{
private readonly T _config;
private readonly IOptionsMonitor<T> _config;
public bool IsMain => true;
public MareConfigurationServiceServer(IOptions<T> config)
public MareConfigurationServiceServer(IOptionsMonitor<T> config)
{
_config = config.Value;
_config = config;
}
public T1 GetValueOrDefault<T1>(string key, T1 defaultValue)
{
return _config.GetValueOrDefault<T1>(key, defaultValue);
return _config.CurrentValue.GetValueOrDefault<T1>(key, defaultValue);
}
public T1 GetValue<T1>(string key)
{
return _config.GetValue<T1>(key);
return _config.CurrentValue.GetValue<T1>(key);
}
public override string ToString()
{
var props = _config.GetType().GetProperties();
var props = _config.CurrentValue.GetType().GetProperties();
StringBuilder sb = new();
foreach (var prop in props)
{
sb.AppendLine($"{prop.Name} (IsRemote: {prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()}) => {prop.GetValue(_config)}");
sb.AppendLine($"{prop.Name} (IsRemote: {prop.GetCustomAttributes(typeof(RemoteConfigurationAttribute), true).Any()}) => {prop.GetValue(_config.CurrentValue)}");
}
sb.AppendLine(_config.ToString());