let file shards register against main or so
This commit is contained in:
@@ -2,3 +2,166 @@
|
|||||||
|
|
||||||
# MA0048: File name must match type name
|
# MA0048: File name must match type name
|
||||||
dotnet_diagnostic.MA0048.severity = suggestion
|
dotnet_diagnostic.MA0048.severity = suggestion
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_using_directive_placement = outside_namespace:silent
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
|
csharp_prefer_braces = true:suggestion
|
||||||
|
csharp_style_namespace_declarations = file_scoped:silent
|
||||||
|
csharp_style_prefer_method_group_conversion = true:suggestion
|
||||||
|
csharp_style_prefer_top_level_statements = false:silent
|
||||||
|
csharp_style_prefer_primary_constructors = true:suggestion
|
||||||
|
csharp_prefer_system_threading_lock = true:suggestion
|
||||||
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
csharp_style_expression_bodied_lambdas = true:silent
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_prefer_tuple_swap = true:suggestion
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
|
|
||||||
|
[*.{cs,vb}]
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
tab_width = 4
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = crlf
|
||||||
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
|
dotnet_style_null_propagation = true:suggestion
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||||
|
dotnet_style_prefer_auto_properties = true:suggestion
|
||||||
|
dotnet_style_object_initializer = true:suggestion
|
||||||
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
|
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||||
|
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
|
||||||
|
dotnet_style_namespace_match_folder = true:suggestion
|
||||||
|
dotnet_style_readonly_field = true:suggestion
|
||||||
|
dotnet_style_qualification_for_field = false:silent
|
||||||
|
[*.cs]
|
||||||
|
#### Naming styles ####
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = warning
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be_fieldstyle.severity = warning
|
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be_fieldstyle.symbols = private_or_internal_field
|
||||||
|
dotnet_naming_rule.private_or_internal_field_should_be_fieldstyle.style = fieldstyle
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
|
||||||
|
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.fieldstyle.required_prefix = _
|
||||||
|
dotnet_naming_style.fieldstyle.required_suffix =
|
||||||
|
dotnet_naming_style.fieldstyle.word_separator =
|
||||||
|
dotnet_naming_style.fieldstyle.capitalization = camel_case
|
||||||
|
|
||||||
|
[*.vb]
|
||||||
|
#### Naming styles ####
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ namespace MareSynchronosShared.Services;
|
|||||||
public interface IConfigurationService<T> where T : class, IMareConfiguration
|
public interface IConfigurationService<T> where T : class, IMareConfiguration
|
||||||
{
|
{
|
||||||
bool IsMain { get; }
|
bool IsMain { get; }
|
||||||
|
|
||||||
|
event EventHandler ConfigChangedEvent;
|
||||||
|
|
||||||
T1 GetValue<T1>(string key);
|
T1 GetValue<T1>(string key);
|
||||||
T1 GetValueOrDefault<T1>(string key, T1 defaultValue);
|
T1 GetValueOrDefault<T1>(string key, T1 defaultValue);
|
||||||
string ToString();
|
string ToString();
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
|
|||||||
private readonly CancellationTokenSource _updateTaskCts = new();
|
private readonly CancellationTokenSource _updateTaskCts = new();
|
||||||
private bool _initialized = false;
|
private bool _initialized = false;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
public event EventHandler ConfigChangedEvent;
|
||||||
|
private IDisposable _onChanged;
|
||||||
|
|
||||||
private Uri GetRoute(string key, string value)
|
private Uri GetRoute(string key, string value)
|
||||||
{
|
{
|
||||||
@@ -39,6 +41,7 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
|
|||||||
|
|
||||||
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptionsMonitor<T> config, ServerTokenGenerator serverTokenGenerator)
|
public MareConfigurationServiceClient(ILogger<MareConfigurationServiceClient<T>> logger, IOptionsMonitor<T> config, ServerTokenGenerator serverTokenGenerator)
|
||||||
{
|
{
|
||||||
|
_onChanged = _config.OnChange((c) => { ConfigChangedEvent?.Invoke(this, EventArgs.Empty); });
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serverTokenGenerator = serverTokenGenerator;
|
_serverTokenGenerator = serverTokenGenerator;
|
||||||
@@ -184,6 +187,7 @@ public class MareConfigurationServiceClient<T> : IHostedService, IConfigurationS
|
|||||||
{
|
{
|
||||||
_updateTaskCts.Cancel();
|
_updateTaskCts.Cancel();
|
||||||
_httpClient.Dispose();
|
_httpClient.Dispose();
|
||||||
|
_onChanged?.Dispose();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,13 +6,18 @@ using System.Text;
|
|||||||
|
|
||||||
namespace MareSynchronosShared.Services;
|
namespace MareSynchronosShared.Services;
|
||||||
|
|
||||||
public class MareConfigurationServiceServer<T> : IConfigurationService<T> where T : class, IMareConfiguration
|
public sealed class MareConfigurationServiceServer<T> : IDisposable, IConfigurationService<T> where T : class, IMareConfiguration
|
||||||
{
|
{
|
||||||
private readonly IOptionsMonitor<T> _config;
|
private readonly IOptionsMonitor<T> _config;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
public bool IsMain => true;
|
public bool IsMain => true;
|
||||||
|
public event EventHandler ConfigChangedEvent;
|
||||||
|
private IDisposable _onChanged;
|
||||||
|
|
||||||
public MareConfigurationServiceServer(IOptionsMonitor<T> config)
|
public MareConfigurationServiceServer(IOptionsMonitor<T> config)
|
||||||
{
|
{
|
||||||
|
_onChanged = _config.OnChange((c) => { ConfigChangedEvent?.Invoke(this, EventArgs.Empty); });
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,4 +53,15 @@ public class MareConfigurationServiceServer<T> : IConfigurationService<T> where
|
|||||||
}
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChanged.Dispose();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace MareSynchronosShared.Utils.Configuration;
|
|
||||||
|
|
||||||
public class CdnShardConfiguration
|
|
||||||
{
|
|
||||||
public List<string> Continents { get; set; }
|
|
||||||
public string FileMatch { get; set; }
|
|
||||||
public Uri CdnFullUrl { get; set; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return CdnFullUrl.ToString() + "[" + string.Join(',', Continents) + "] == " + FileMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace MareSynchronosShared.Utils.Configuration;
|
||||||
|
|
||||||
|
public class ShardConfiguration
|
||||||
|
{
|
||||||
|
public List<string> Continents { get; set; }
|
||||||
|
public string FileMatch { get; set; }
|
||||||
|
public Dictionary<string, Uri> RegionUris { get; set; }
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using MareSynchronosShared.Utils;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MareSynchronosShared.Utils.Configuration;
|
namespace MareSynchronosShared.Utils.Configuration;
|
||||||
|
|
||||||
@@ -25,8 +24,7 @@ public class StaticFilesServerConfiguration : MareConfigurationBase
|
|||||||
public double SpeedTestHoursRateLimit { get; set; } = 0.5;
|
public double SpeedTestHoursRateLimit { get; set; } = 0.5;
|
||||||
[RemoteConfiguration]
|
[RemoteConfiguration]
|
||||||
public Uri CdnFullUrl { get; set; } = null;
|
public Uri CdnFullUrl { get; set; } = null;
|
||||||
[RemoteConfiguration]
|
public ShardConfiguration? ShardConfiguration { get; set; } = null;
|
||||||
public List<CdnShardConfiguration> CdnShardConfiguration { get; set; } = new();
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
@@ -42,7 +40,6 @@ public class StaticFilesServerConfiguration : MareConfigurationBase
|
|||||||
sb.AppendLine($"{nameof(CacheDirectory)} => {CacheDirectory}");
|
sb.AppendLine($"{nameof(CacheDirectory)} => {CacheDirectory}");
|
||||||
sb.AppendLine($"{nameof(DownloadQueueSize)} => {DownloadQueueSize}");
|
sb.AppendLine($"{nameof(DownloadQueueSize)} => {DownloadQueueSize}");
|
||||||
sb.AppendLine($"{nameof(DownloadQueueReleaseSeconds)} => {DownloadQueueReleaseSeconds}");
|
sb.AppendLine($"{nameof(DownloadQueueReleaseSeconds)} => {DownloadQueueReleaseSeconds}");
|
||||||
sb.AppendLine($"{nameof(CdnShardConfiguration)} => {string.Join(", ", CdnShardConfiguration)}");
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MareSynchronos.API.Routes;
|
using MareSynchronos.API.Routes;
|
||||||
|
using MareSynchronosShared.Utils.Configuration;
|
||||||
using MareSynchronosStaticFilesServer.Services;
|
using MareSynchronosStaticFilesServer.Services;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -6,20 +7,60 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
namespace MareSynchronosStaticFilesServer.Controllers;
|
namespace MareSynchronosStaticFilesServer.Controllers;
|
||||||
|
|
||||||
[Route(MareFiles.Main)]
|
[Route(MareFiles.Main)]
|
||||||
|
[Authorize(Policy = "Internal")]
|
||||||
public class MainController : ControllerBase
|
public class MainController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IClientReadyMessageService _messageService;
|
private readonly IClientReadyMessageService _messageService;
|
||||||
|
private readonly MainServerShardRegistrationService _shardRegistrationService;
|
||||||
|
|
||||||
public MainController(ILogger<MainController> logger, IClientReadyMessageService mareHub) : base(logger)
|
public MainController(ILogger<MainController> logger, IClientReadyMessageService mareHub,
|
||||||
|
MainServerShardRegistrationService shardRegistrationService) : base(logger)
|
||||||
{
|
{
|
||||||
_messageService = mareHub;
|
_messageService = mareHub;
|
||||||
|
_shardRegistrationService = shardRegistrationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet(MareFiles.Main_SendReady)]
|
[HttpGet(MareFiles.Main_SendReady)]
|
||||||
[Authorize(Policy = "Internal")]
|
|
||||||
public async Task<IActionResult> SendReadyToClients(string uid, Guid requestId)
|
public async Task<IActionResult> SendReadyToClients(string uid, Guid requestId)
|
||||||
{
|
{
|
||||||
await _messageService.SendDownloadReady(uid, requestId).ConfigureAwait(false);
|
await _messageService.SendDownloadReady(uid, requestId).ConfigureAwait(false);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("shardRegister")]
|
||||||
|
public IActionResult RegisterShard([FromBody] ShardConfiguration shardConfiguration)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_shardRegistrationService.RegisterShard(MareUser, shardConfiguration);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Shard could not be registered {shard}", MareUser);
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("shardUnregister")]
|
||||||
|
public IActionResult UnregisterShard()
|
||||||
|
{
|
||||||
|
_shardRegistrationService.UnregisterShard(MareUser);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("shardHeartbeat")]
|
||||||
|
public IActionResult ShardHeartbeat()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_shardRegistrationService.ShardHeartbeat(MareUser);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Shard not registered: {shard}", MareUser);
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -31,11 +31,13 @@ public class ServerFilesController : ControllerBase
|
|||||||
private readonly IHubContext<MareHub> _hubContext;
|
private readonly IHubContext<MareHub> _hubContext;
|
||||||
private readonly IDbContextFactory<MareDbContext> _mareDbContext;
|
private readonly IDbContextFactory<MareDbContext> _mareDbContext;
|
||||||
private readonly MareMetrics _metricsClient;
|
private readonly MareMetrics _metricsClient;
|
||||||
|
private readonly MainServerShardRegistrationService _shardRegistrationService;
|
||||||
|
|
||||||
public ServerFilesController(ILogger<ServerFilesController> logger, CachedFileProvider cachedFileProvider,
|
public ServerFilesController(ILogger<ServerFilesController> logger, CachedFileProvider cachedFileProvider,
|
||||||
IConfigurationService<StaticFilesServerConfiguration> configuration,
|
IConfigurationService<StaticFilesServerConfiguration> configuration,
|
||||||
IHubContext<MareHub> hubContext,
|
IHubContext<MareHub> hubContext,
|
||||||
IDbContextFactory<MareDbContext> mareDbContext, MareMetrics metricsClient) : base(logger)
|
IDbContextFactory<MareDbContext> mareDbContext, MareMetrics metricsClient,
|
||||||
|
MainServerShardRegistrationService shardRegistrationService) : base(logger)
|
||||||
{
|
{
|
||||||
_basePath = configuration.GetValueOrDefault(nameof(StaticFilesServerConfiguration.UseColdStorage), false)
|
_basePath = configuration.GetValueOrDefault(nameof(StaticFilesServerConfiguration.UseColdStorage), false)
|
||||||
? configuration.GetValue<string>(nameof(StaticFilesServerConfiguration.ColdStorageDirectory))
|
? configuration.GetValue<string>(nameof(StaticFilesServerConfiguration.ColdStorageDirectory))
|
||||||
@@ -45,6 +47,7 @@ public class ServerFilesController : ControllerBase
|
|||||||
_hubContext = hubContext;
|
_hubContext = hubContext;
|
||||||
_mareDbContext = mareDbContext;
|
_mareDbContext = mareDbContext;
|
||||||
_metricsClient = metricsClient;
|
_metricsClient = metricsClient;
|
||||||
|
_shardRegistrationService = shardRegistrationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(MareFiles.ServerFiles_DeleteAll)]
|
[HttpPost(MareFiles.ServerFiles_DeleteAll)]
|
||||||
@@ -85,7 +88,7 @@ public class ServerFilesController : ControllerBase
|
|||||||
.Select(k => new { k.Hash, k.Size, k.RawSize })
|
.Select(k => new { k.Hash, k.Size, k.RawSize })
|
||||||
.ToListAsync().ConfigureAwait(false);
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var allFileShards = new List<CdnShardConfiguration>(_configuration.GetValueOrDefault(nameof(StaticFilesServerConfiguration.CdnShardConfiguration), new List<CdnShardConfiguration>()));
|
var allFileShards = _shardRegistrationService.GetConfigurationsByContinent(Continent);
|
||||||
|
|
||||||
foreach (var file in cacheFile)
|
foreach (var file in cacheFile)
|
||||||
{
|
{
|
||||||
@@ -94,25 +97,12 @@ public class ServerFilesController : ControllerBase
|
|||||||
|
|
||||||
if (forbiddenFile == null)
|
if (forbiddenFile == null)
|
||||||
{
|
{
|
||||||
List<CdnShardConfiguration> selectedShards = new();
|
|
||||||
var matchingShards = allFileShards.Where(f => new Regex(f.FileMatch).IsMatch(file.Hash)).ToList();
|
var matchingShards = allFileShards.Where(f => new Regex(f.FileMatch).IsMatch(file.Hash)).ToList();
|
||||||
|
|
||||||
if (string.Equals(Continent, "*", StringComparison.Ordinal))
|
var shard = matchingShards.SelectMany(g => g.RegionUris)
|
||||||
{
|
.OrderBy(g => Guid.NewGuid()).FirstOrDefault();
|
||||||
selectedShards = matchingShards;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
selectedShards = matchingShards.Where(c => c.Continents.Contains(Continent, StringComparer.OrdinalIgnoreCase)).ToList();
|
|
||||||
if (!selectedShards.Any()) selectedShards = matchingShards;
|
|
||||||
}
|
|
||||||
|
|
||||||
var shard = selectedShards
|
baseUrl = shard.Value ?? _configuration.GetValue<Uri>(nameof(StaticFilesServerConfiguration.CdnFullUrl));
|
||||||
.OrderBy(s => !s.Continents.Any() ? 0 : 1)
|
|
||||||
.ThenBy(s => s.Continents.Contains("*", StringComparer.Ordinal) ? 0 : 1)
|
|
||||||
.ThenBy(g => Guid.NewGuid()).FirstOrDefault();
|
|
||||||
|
|
||||||
baseUrl = shard?.CdnFullUrl ?? _configuration.GetValue<Uri>(nameof(StaticFilesServerConfiguration.CdnFullUrl));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Add(new DownloadFileDto
|
response.Add(new DownloadFileDto
|
||||||
@@ -133,15 +123,8 @@ public class ServerFilesController : ControllerBase
|
|||||||
[HttpGet(MareFiles.ServerFiles_DownloadServers)]
|
[HttpGet(MareFiles.ServerFiles_DownloadServers)]
|
||||||
public async Task<IActionResult> GetDownloadServers()
|
public async Task<IActionResult> GetDownloadServers()
|
||||||
{
|
{
|
||||||
var allFileShards = new List<CdnShardConfiguration>(_configuration.GetValueOrDefault(nameof(StaticFilesServerConfiguration.CdnShardConfiguration), new List<CdnShardConfiguration>()))
|
var allFileShards = _shardRegistrationService.GetConfigurationsByContinent(Continent);
|
||||||
.DistinctBy(f => f.CdnFullUrl).ToList();
|
return Ok(JsonSerializer.Serialize(allFileShards.SelectMany(t => t.RegionUris.Select(v => v.Value.ToString()))));
|
||||||
if (!allFileShards.Any())
|
|
||||||
{
|
|
||||||
return Ok(JsonSerializer.Serialize(new List<string> { _configuration.GetValue<Uri>(nameof(StaticFilesServerConfiguration.CdnFullUrl)).ToString() }));
|
|
||||||
}
|
|
||||||
var selectedShards = allFileShards.Where(c => c.Continents.Contains(Continent, StringComparer.OrdinalIgnoreCase)).ToList();
|
|
||||||
if (!selectedShards.Any()) selectedShards = allFileShards.Where(c => c.Continents.Contains("*", StringComparer.Ordinal)).ToList();
|
|
||||||
return Ok(JsonSerializer.Serialize(selectedShards.Select(t => t.CdnFullUrl.ToString())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(MareFiles.ServerFiles_FilesSend)]
|
[HttpPost(MareFiles.ServerFiles_FilesSend)]
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,6 +98,8 @@ public class Startup
|
|||||||
services.AddSingleton<IClientReadyMessageService, MainClientReadyMessageService>();
|
services.AddSingleton<IClientReadyMessageService, MainClientReadyMessageService>();
|
||||||
services.AddHostedService<MainFileCleanupService>();
|
services.AddHostedService<MainFileCleanupService>();
|
||||||
services.AddSingleton<IConfigurationService<StaticFilesServerConfiguration>, MareConfigurationServiceServer<StaticFilesServerConfiguration>>();
|
services.AddSingleton<IConfigurationService<StaticFilesServerConfiguration>, MareConfigurationServiceServer<StaticFilesServerConfiguration>>();
|
||||||
|
services.AddSingleton<MainServerShardRegistrationService>();
|
||||||
|
services.AddHostedService(s => s.GetRequiredService<MainServerShardRegistrationService>());
|
||||||
services.AddDbContextPool<MareDbContext>(options =>
|
services.AddDbContextPool<MareDbContext>(options =>
|
||||||
{
|
{
|
||||||
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
|
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"), builder =>
|
||||||
@@ -180,6 +182,8 @@ public class Startup
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
services.AddSingleton<ShardRegistrationService>();
|
||||||
|
services.AddHostedService(s => s.GetRequiredService<ShardRegistrationService>());
|
||||||
services.AddSingleton<IClientReadyMessageService, ShardClientReadyMessageService>();
|
services.AddSingleton<IClientReadyMessageService, ShardClientReadyMessageService>();
|
||||||
services.AddHostedService<ShardFileCleanupService>();
|
services.AddHostedService<ShardFileCleanupService>();
|
||||||
services.AddSingleton<IConfigurationService<StaticFilesServerConfiguration>, MareConfigurationServiceClient<StaticFilesServerConfiguration>>();
|
services.AddSingleton<IConfigurationService<StaticFilesServerConfiguration>, MareConfigurationServiceClient<StaticFilesServerConfiguration>>();
|
||||||
|
|||||||
Reference in New Issue
Block a user