let file shards register against main or so
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
using MareSynchronosShared.Services;
|
||||
using MareSynchronosShared.Utils.Configuration;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace MareSynchronosStaticFilesServer.Services;
|
||||
|
||||
public class MainServerShardRegistrationService : IHostedService
|
||||
{
|
||||
private readonly ILogger<MainServerShardRegistrationService> _logger;
|
||||
private readonly IConfigurationService<StaticFilesServerConfiguration> _configurationService;
|
||||
private readonly ConcurrentDictionary<string, ShardConfiguration> _shardConfigs = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<string, DateTime> _shardHeartbeats = new(StringComparer.Ordinal);
|
||||
private readonly CancellationTokenSource _periodicCheckCts = new();
|
||||
|
||||
public MainServerShardRegistrationService(ILogger<MainServerShardRegistrationService> logger,
|
||||
IConfigurationService<StaticFilesServerConfiguration> configurationService)
|
||||
{
|
||||
_logger = logger;
|
||||
_configurationService = configurationService;
|
||||
}
|
||||
|
||||
public void RegisterShard(string shardName, ShardConfiguration shardConfiguration)
|
||||
{
|
||||
if (shardConfiguration == null || shardConfiguration == default)
|
||||
throw new InvalidOperationException("Empty configuration provided");
|
||||
|
||||
if (_shardConfigs.ContainsKey(shardName))
|
||||
_logger.LogInformation("Re-Registering Shard {name}", shardName);
|
||||
else
|
||||
_logger.LogInformation("Registering Shard {name}", shardName);
|
||||
|
||||
_shardHeartbeats[shardName] = DateTime.UtcNow;
|
||||
_shardConfigs[shardName] = shardConfiguration;
|
||||
}
|
||||
|
||||
public void UnregisterShard(string shardName)
|
||||
{
|
||||
_logger.LogInformation("Unregistering Shard {name}", shardName);
|
||||
|
||||
_shardHeartbeats.TryRemove(shardName, out _);
|
||||
_shardConfigs.TryRemove(shardName, out _);
|
||||
}
|
||||
|
||||
public List<ShardConfiguration> GetConfigurationsByContinent(string continent)
|
||||
{
|
||||
var shardConfigs = _shardConfigs.Values.Where(v => v.Continents.Contains(continent, StringComparer.OrdinalIgnoreCase)).ToList();
|
||||
if (shardConfigs.Any()) return shardConfigs;
|
||||
shardConfigs = _shardConfigs.Values.Where(v => v.Continents.Contains("*", StringComparer.OrdinalIgnoreCase)).ToList();
|
||||
if (shardConfigs.Any()) return shardConfigs;
|
||||
return [new ShardConfiguration() {
|
||||
Continents = ["*"],
|
||||
FileMatch = ".*",
|
||||
RegionUris = new(StringComparer.Ordinal) {
|
||||
{ "Central", _configurationService.GetValue<Uri>(nameof(StaticFilesServerConfiguration.CdnFullUrl)) }
|
||||
} }];
|
||||
}
|
||||
|
||||
public void ShardHeartbeat(string shardName)
|
||||
{
|
||||
if (!_shardConfigs.ContainsKey(shardName))
|
||||
throw new InvalidOperationException("Shard not registered");
|
||||
|
||||
_logger.LogInformation("Heartbeat from {name}", shardName);
|
||||
_shardHeartbeats[shardName] = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_ = Task.Run(() => PeriodicHeartbeatCleanup(_periodicCheckCts.Token), cancellationToken).ConfigureAwait(false);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await _periodicCheckCts.CancelAsync().ConfigureAwait(false);
|
||||
_periodicCheckCts.Dispose();
|
||||
}
|
||||
|
||||
private async Task PeriodicHeartbeatCleanup(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
foreach (var kvp in _shardHeartbeats.ToFrozenDictionary())
|
||||
{
|
||||
if (DateTime.UtcNow.Subtract(kvp.Value) > TimeSpan.FromMinutes(1))
|
||||
{
|
||||
_shardHeartbeats.TryRemove(kvp.Key, out _);
|
||||
_shardConfigs.TryRemove(kvp.Key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(5000, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using MareSynchronos.API.Routes;
|
||||
using MareSynchronosShared.Services;
|
||||
using MareSynchronosShared.Utils;
|
||||
using MareSynchronosShared.Utils.Configuration;
|
||||
|
||||
namespace MareSynchronosStaticFilesServer.Services;
|
||||
|
||||
public class ShardRegistrationService : IHostedService
|
||||
{
|
||||
private readonly ILogger<ShardRegistrationService> _logger;
|
||||
private readonly IConfigurationService<StaticFilesServerConfiguration> _configurationService;
|
||||
private readonly HttpClient _httpClient = new();
|
||||
private readonly CancellationTokenSource _heartBeatCts = new();
|
||||
private bool _isRegistered = false;
|
||||
|
||||
public ShardRegistrationService(ILogger<ShardRegistrationService> logger,
|
||||
IConfigurationService<StaticFilesServerConfiguration> configurationService,
|
||||
ServerTokenGenerator serverTokenGenerator)
|
||||
{
|
||||
_logger = logger;
|
||||
_configurationService = configurationService;
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", serverTokenGenerator.Token);
|
||||
}
|
||||
|
||||
private void OnConfigChanged(object sender, EventArgs e)
|
||||
{
|
||||
_isRegistered = false;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Starting");
|
||||
_configurationService.ConfigChangedEvent += OnConfigChanged;
|
||||
_ = Task.Run(() => HeartbeatLoop(_heartBeatCts.Token));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Stopping");
|
||||
|
||||
_configurationService.ConfigChangedEvent -= OnConfigChanged;
|
||||
_heartBeatCts.Cancel();
|
||||
_heartBeatCts.Dispose();
|
||||
// call unregister
|
||||
await UnregisterShard().ConfigureAwait(false);
|
||||
_httpClient.Dispose();
|
||||
}
|
||||
|
||||
private async Task HeartbeatLoop(CancellationToken ct)
|
||||
{
|
||||
while (!_heartBeatCts.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessHeartbeat(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Issue during Heartbeat");
|
||||
_isRegistered = false;
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessHeartbeat(CancellationToken ct)
|
||||
{
|
||||
if (!_isRegistered)
|
||||
{
|
||||
await TryRegisterShard(ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await ShardHeartbeat(ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ShardHeartbeat(CancellationToken ct)
|
||||
{
|
||||
Uri mainServer = _configurationService.GetValue<Uri>(nameof(StaticFilesServerConfiguration.MainFileServerAddress));
|
||||
_logger.LogInformation("Running heartbeat against Main {server}", mainServer);
|
||||
|
||||
using var heartBeat = await _httpClient.PostAsync(new Uri(mainServer, MareFiles.Main + "/shardHeartbeat"), null, ct).ConfigureAwait(false);
|
||||
heartBeat.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
private async Task TryRegisterShard(CancellationToken ct)
|
||||
{
|
||||
Uri mainServer = _configurationService.GetValue<Uri>(nameof(StaticFilesServerConfiguration.MainFileServerAddress));
|
||||
_logger.LogInformation("Registering Shard with Main {server}", mainServer);
|
||||
var config = _configurationService.GetValue<ShardConfiguration>(nameof(StaticFilesServerConfiguration.ShardConfiguration));
|
||||
_logger.LogInformation("Config Value {varName}: {value}", nameof(ShardConfiguration.Continents), string.Join(", ", config.Continents));
|
||||
_logger.LogInformation("Config Value {varName}: {value}", nameof(ShardConfiguration.FileMatch), config.FileMatch);
|
||||
_logger.LogInformation("Config Value {varName}: {value}", nameof(ShardConfiguration.RegionUris), string.Join("; ", config.RegionUris.Select(k => k.Key + ":" + k.Value)));
|
||||
|
||||
using var register = await _httpClient.PostAsJsonAsync(new Uri(mainServer, MareFiles.Main + "/shardRegister"), config, ct).ConfigureAwait(false);
|
||||
register.EnsureSuccessStatusCode();
|
||||
_isRegistered = true;
|
||||
}
|
||||
|
||||
private async Task UnregisterShard()
|
||||
{
|
||||
Uri mainServer = _configurationService.GetValue<Uri>(nameof(StaticFilesServerConfiguration.MainFileServerAddress));
|
||||
_logger.LogInformation("Unregistering Shard with Main {server}", mainServer);
|
||||
using var heartBeat = await _httpClient.PostAsync(new Uri(mainServer, MareFiles.Main + "/shardUnregister"), null).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user