Switch to GrpcClientIdentificationService and abolish Redis for Idents (#12)
* add GrpcClientIdentificationService * remove unnecessary gauges * set to no retry policy * initialize metrics Co-authored-by: Stanley Dimant <root.darkarchon@outlook.com>
This commit is contained in:
@@ -8,9 +8,7 @@ using MareSynchronosServer.Utils;
|
|||||||
using MareSynchronosShared.Authentication;
|
using MareSynchronosShared.Authentication;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Models;
|
|
||||||
using MareSynchronosShared.Protos;
|
using MareSynchronosShared.Protos;
|
||||||
using MareSynchronosShared.Services;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
@@ -27,7 +25,7 @@ public partial class MareHub : Hub
|
|||||||
private readonly FileService.FileServiceClient _fileServiceClient;
|
private readonly FileService.FileServiceClient _fileServiceClient;
|
||||||
private readonly SystemInfoService _systemInfoService;
|
private readonly SystemInfoService _systemInfoService;
|
||||||
private readonly IHttpContextAccessor _contextAccessor;
|
private readonly IHttpContextAccessor _contextAccessor;
|
||||||
private readonly IClientIdentificationService _clientIdentService;
|
private readonly GrpcClientIdentificationService _clientIdentService;
|
||||||
private readonly MareHubLogger _logger;
|
private readonly MareHubLogger _logger;
|
||||||
private readonly MareDbContext _dbContext;
|
private readonly MareDbContext _dbContext;
|
||||||
private readonly Uri _cdnFullUri;
|
private readonly Uri _cdnFullUri;
|
||||||
@@ -38,7 +36,7 @@ public partial class MareHub : Hub
|
|||||||
|
|
||||||
public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient,
|
public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient,
|
||||||
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor,
|
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor,
|
||||||
IClientIdentificationService clientIdentService)
|
GrpcClientIdentificationService clientIdentService)
|
||||||
{
|
{
|
||||||
_mareMetrics = mareMetrics;
|
_mareMetrics = mareMetrics;
|
||||||
_authServiceClient = authServiceClient;
|
_authServiceClient = authServiceClient;
|
||||||
@@ -118,9 +116,11 @@ public partial class MareHub : Hub
|
|||||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||||
public async Task<bool> CheckClientHealth()
|
public async Task<bool> CheckClientHealth()
|
||||||
{
|
{
|
||||||
var needsReconnect = string.IsNullOrEmpty(await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false));
|
var serverId = await _clientIdentService.GetServerForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||||
if (needsReconnect)
|
bool needsReconnect = false;
|
||||||
|
if (string.IsNullOrEmpty(serverId) || !string.Equals(serverId, _shardName, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
|
needsReconnect = true;
|
||||||
_logger.LogCallWarning(Api.InvokeCheckClientHealth, needsReconnect);
|
_logger.LogCallWarning(Api.InvokeCheckClientHealth, needsReconnect);
|
||||||
}
|
}
|
||||||
return needsReconnect;
|
return needsReconnect;
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using MareSynchronosShared.Metrics;
|
||||||
|
using MareSynchronosShared.Protos;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MareSynchronosServer.Services;
|
||||||
|
|
||||||
|
public class GrpcClientIdentificationService : IHostedService
|
||||||
|
{
|
||||||
|
private readonly string _shardName;
|
||||||
|
private readonly ILogger<GrpcClientIdentificationService> _logger;
|
||||||
|
private readonly IdentificationService.IdentificationServiceClient _grpcIdentClient;
|
||||||
|
private readonly MareMetrics _metrics;
|
||||||
|
protected ConcurrentDictionary<string, string> OnlineClients = new(StringComparer.Ordinal);
|
||||||
|
private bool _grpcIsFaulty = false;
|
||||||
|
|
||||||
|
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger, IdentificationService.IdentificationServiceClient gprcIdentClient, MareMetrics metrics, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var config = configuration.GetSection("MareSynchronos");
|
||||||
|
_shardName = config.GetValue("ServerName", "Main");
|
||||||
|
_logger = logger;
|
||||||
|
_grpcIdentClient = gprcIdentClient;
|
||||||
|
_metrics = metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetCharacterIdentForUid(string uid)
|
||||||
|
{
|
||||||
|
if (OnlineClients.TryGetValue(uid, out string ident))
|
||||||
|
{
|
||||||
|
return ident;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await InvokeOnGrpc(_grpcIdentClient.GetIdentForUidAsync(new UidMessage { Uid = uid })).ConfigureAwait(false);
|
||||||
|
if (result == default(CharacterIdentMessage)) return null;
|
||||||
|
return result.Ident;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetServerForUid(string uid)
|
||||||
|
{
|
||||||
|
if (OnlineClients.ContainsKey(uid))
|
||||||
|
{
|
||||||
|
return _shardName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await InvokeOnGrpc(_grpcIdentClient.GetIdentForUidAsync(new UidMessage { Uid = uid })).ConfigureAwait(false);
|
||||||
|
if (result == default(CharacterIdentMessage)) return null;
|
||||||
|
return result.ServerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetOnlineUsers()
|
||||||
|
{
|
||||||
|
var result = await InvokeOnGrpc(_grpcIdentClient.GetOnlineUserCountAsync(new ServerMessage())).ConfigureAwait(false);
|
||||||
|
if (result == default(OnlineUserCountResponse)) return OnlineClients.Count;
|
||||||
|
return result.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetUidForCharacterIdent(string characterIdent)
|
||||||
|
{
|
||||||
|
bool existsLocal = OnlineClients.Any(o => string.Equals(o.Value, characterIdent, StringComparison.Ordinal));
|
||||||
|
if (existsLocal)
|
||||||
|
{
|
||||||
|
return OnlineClients.First(c => string.Equals(c.Value, characterIdent, StringComparison.Ordinal)).Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await InvokeOnGrpc(_grpcIdentClient.GetUidForCharacterIdentAsync(new CharacterIdentMessage { Ident = characterIdent, ServerId = string.Empty })).ConfigureAwait(false);
|
||||||
|
if (result == default(UidMessage)) return null;
|
||||||
|
return result.Uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MarkUserOffline(string uid)
|
||||||
|
{
|
||||||
|
OnlineClients.TryRemove(uid, out _);
|
||||||
|
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||||
|
await ExecuteOnGrpc(_grpcIdentClient.RemoveIdentForUidAsync(new RemoveIdentMessage() { ServerId = _shardName, Uid = uid })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MarkUserOnline(string uid, string charaIdent)
|
||||||
|
{
|
||||||
|
OnlineClients[uid] = charaIdent;
|
||||||
|
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||||
|
await ExecuteOnGrpc(_grpcIdentClient.SetIdentForUidAsync(new SetIdentMessage() { Ident = charaIdent, ServerId = _shardName, Uid = uid })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<T> InvokeOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await toExecute.ConfigureAwait(false);
|
||||||
|
|
||||||
|
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
SetGrpcFaulty();
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await toExecute.ConfigureAwait(false);
|
||||||
|
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
SetGrpcFaulty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckFaultStateAndResend()
|
||||||
|
{
|
||||||
|
if (_grpcIsFaulty)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("GRPC connection is restored, sending current server idents");
|
||||||
|
await _grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }).ConfigureAwait(false);
|
||||||
|
var msg = new ServerIdentMessage();
|
||||||
|
msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage()
|
||||||
|
{
|
||||||
|
Ident = c.Value,
|
||||||
|
Uid = c.Key,
|
||||||
|
ServerId = _shardName
|
||||||
|
}));
|
||||||
|
await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false);
|
||||||
|
_grpcIsFaulty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGrpcFaulty()
|
||||||
|
{
|
||||||
|
if (!_grpcIsFaulty)
|
||||||
|
{
|
||||||
|
_grpcIsFaulty = true;
|
||||||
|
_logger.LogWarning("GRPC connection is faulty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ using MareSynchronos.API;
|
|||||||
using MareSynchronosServer.Hubs;
|
using MareSynchronosServer.Hubs;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Services;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -19,14 +18,14 @@ public class SystemInfoService : IHostedService, IDisposable
|
|||||||
{
|
{
|
||||||
private readonly MareMetrics _mareMetrics;
|
private readonly MareMetrics _mareMetrics;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
private readonly IClientIdentificationService _clientIdentService;
|
private readonly GrpcClientIdentificationService _clientIdentService;
|
||||||
private readonly ILogger<SystemInfoService> _logger;
|
private readonly ILogger<SystemInfoService> _logger;
|
||||||
private readonly IHubContext<MareHub> _hubContext;
|
private readonly IHubContext<MareHub> _hubContext;
|
||||||
private Timer _timer;
|
private Timer _timer;
|
||||||
private string _shardName;
|
private string _shardName;
|
||||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||||
|
|
||||||
public SystemInfoService(MareMetrics mareMetrics, IConfiguration configuration, IServiceProvider services, IClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
|
public SystemInfoService(MareMetrics mareMetrics, IConfiguration configuration, IServiceProvider services, GrpcClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
|
||||||
{
|
{
|
||||||
_mareMetrics = mareMetrics;
|
_mareMetrics = mareMetrics;
|
||||||
_services = services;
|
_services = services;
|
||||||
@@ -56,7 +55,7 @@ public class SystemInfoService : IHostedService, IDisposable
|
|||||||
{
|
{
|
||||||
SystemInfoDto = new SystemInfoDto()
|
SystemInfoDto = new SystemInfoDto()
|
||||||
{
|
{
|
||||||
OnlineUsers = _clientIdentService.GetOnlineUsers().Result,
|
OnlineUsers = (int)_clientIdentService.GetOnlineUsers().Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
_hubContext.Clients.All.SendAsync(Api.OnUpdateSystemInfo, SystemInfoDto);
|
_hubContext.Clients.All.SendAsync(Api.OnUpdateSystemInfo, SystemInfoDto);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ using Prometheus;
|
|||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MareSynchronosServer.Services;
|
using MareSynchronosServer.Services;
|
||||||
using MareSynchronosShared.Services;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using MareSynchronosServer.Utils;
|
using MareSynchronosServer.Utils;
|
||||||
|
|
||||||
@@ -64,6 +63,12 @@ public class Startup
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var identMethodConfig = new MethodConfig
|
||||||
|
{
|
||||||
|
Names = { MethodName.Default },
|
||||||
|
RetryPolicy = null
|
||||||
|
};
|
||||||
|
|
||||||
services.AddSingleton(new MareMetrics(new List<string>
|
services.AddSingleton(new MareMetrics(new List<string>
|
||||||
{
|
{
|
||||||
MetricsAPI.CounterInitializedConnections,
|
MetricsAPI.CounterInitializedConnections,
|
||||||
@@ -77,7 +82,10 @@ public class Startup
|
|||||||
MetricsAPI.GaugePairs,
|
MetricsAPI.GaugePairs,
|
||||||
MetricsAPI.GaugePairsPaused,
|
MetricsAPI.GaugePairsPaused,
|
||||||
MetricsAPI.GaugeAvailableIOWorkerThreads,
|
MetricsAPI.GaugeAvailableIOWorkerThreads,
|
||||||
MetricsAPI.GaugeAvailableWorkerThreads
|
MetricsAPI.GaugeAvailableWorkerThreads,
|
||||||
|
MetricsAPI.GaugeGroups,
|
||||||
|
MetricsAPI.GaugeGroupPairs,
|
||||||
|
MetricsAPI.GaugeGroupPairsPaused
|
||||||
}));
|
}));
|
||||||
|
|
||||||
services.AddGrpcClient<AuthService.AuthServiceClient>(c =>
|
services.AddGrpcClient<AuthService.AuthServiceClient>(c =>
|
||||||
@@ -98,6 +106,20 @@ public class Startup
|
|||||||
{
|
{
|
||||||
c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } };
|
c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } };
|
||||||
});
|
});
|
||||||
|
services.AddGrpcClient<IdentificationService.IdentificationServiceClient>(c =>
|
||||||
|
{
|
||||||
|
c.Address = new Uri(mareConfig.GetValue<string>("ServiceAddress"));
|
||||||
|
}).ConfigureChannel(c =>
|
||||||
|
{
|
||||||
|
c.ServiceConfig = new ServiceConfig { MethodConfigs = { identMethodConfig } };
|
||||||
|
c.HttpHandler = new SocketsHttpHandler()
|
||||||
|
{
|
||||||
|
EnableMultipleHttp2Connections = true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<GrpcClientIdentificationService>();
|
||||||
|
services.AddHostedService(p => p.GetService<GrpcClientIdentificationService>());
|
||||||
|
|
||||||
services.AddDbContextPool<MareDbContext>(options =>
|
services.AddDbContextPool<MareDbContext>(options =>
|
||||||
{
|
{
|
||||||
@@ -135,19 +157,6 @@ public class Startup
|
|||||||
{
|
{
|
||||||
options.Configuration.ChannelPrefix = "MareSynchronos";
|
options.Configuration.ChannelPrefix = "MareSynchronos";
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddStackExchangeRedisCache(opt =>
|
|
||||||
{
|
|
||||||
opt.Configuration = redis;
|
|
||||||
opt.InstanceName = "MareSynchronosCache:";
|
|
||||||
});
|
|
||||||
services.AddSingleton<IClientIdentificationService, DistributedClientIdentificationService>();
|
|
||||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
|
|
||||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ using Discord;
|
|||||||
using Discord.Rest;
|
using Discord.Rest;
|
||||||
using Discord.WebSocket;
|
using Discord.WebSocket;
|
||||||
using MareSynchronosServices.Authentication;
|
using MareSynchronosServices.Authentication;
|
||||||
|
using MareSynchronosServices.Identity;
|
||||||
using MareSynchronosShared.Data;
|
using MareSynchronosShared.Data;
|
||||||
using MareSynchronosShared.Metrics;
|
using MareSynchronosShared.Metrics;
|
||||||
using MareSynchronosShared.Models;
|
using MareSynchronosShared.Models;
|
||||||
using MareSynchronosShared.Services;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -24,11 +24,11 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace MareSynchronosServices.Discord;
|
namespace MareSynchronosServices.Discord;
|
||||||
|
|
||||||
public class DiscordBot : IHostedService
|
internal class DiscordBot : IHostedService
|
||||||
{
|
{
|
||||||
private readonly CleanupService cleanupService;
|
private readonly CleanupService cleanupService;
|
||||||
private readonly MareMetrics metrics;
|
private readonly MareMetrics metrics;
|
||||||
private readonly IClientIdentificationService clientService;
|
private readonly IdentityHandler identityHandler;
|
||||||
private readonly IServiceProvider services;
|
private readonly IServiceProvider services;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly ILogger<DiscordBot> logger;
|
private readonly ILogger<DiscordBot> logger;
|
||||||
@@ -44,16 +44,15 @@ public class DiscordBot : IHostedService
|
|||||||
private ConcurrentDictionary<ulong, DateTime> LastVanityChange = new();
|
private ConcurrentDictionary<ulong, DateTime> LastVanityChange = new();
|
||||||
private ConcurrentDictionary<string, DateTime> LastVanityGidChange = new();
|
private ConcurrentDictionary<string, DateTime> LastVanityGidChange = new();
|
||||||
private ulong vanityCommandId;
|
private ulong vanityCommandId;
|
||||||
private ulong vanityGidCommandId;
|
|
||||||
private Task cleanUpUserTask = null;
|
private Task cleanUpUserTask = null;
|
||||||
|
|
||||||
private SemaphoreSlim semaphore;
|
private SemaphoreSlim semaphore;
|
||||||
|
|
||||||
public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IClientIdentificationService clientService, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger)
|
public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IdentityHandler identityHandler, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger)
|
||||||
{
|
{
|
||||||
this.cleanupService = cleanupService;
|
this.cleanupService = cleanupService;
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
this.clientService = clientService;
|
this.identityHandler = identityHandler;
|
||||||
this.services = services;
|
this.services = services;
|
||||||
_configuration = configuration.GetRequiredSection("MareSynchronos");
|
_configuration = configuration.GetRequiredSection("MareSynchronos");
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -804,7 +803,7 @@ public class DiscordBot : IHostedService
|
|||||||
updateStatusCts = new();
|
updateStatusCts = new();
|
||||||
while (!updateStatusCts.IsCancellationRequested)
|
while (!updateStatusCts.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var onlineUsers = await clientService.GetOnlineUsers();
|
var onlineUsers = identityHandler.GetOnlineUsers(string.Empty);
|
||||||
logger.LogInformation("Users online: " + onlineUsers);
|
logger.LogInformation("Users online: " + onlineUsers);
|
||||||
await discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers + " Users")).ConfigureAwait(false);
|
await discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers + " Users")).ConfigureAwait(false);
|
||||||
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MareSynchronosServices.Identity;
|
||||||
|
|
||||||
|
internal class IdentityHandler
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, ServerIdentity> cachedIdentities = new();
|
||||||
|
|
||||||
|
internal Task<string> GetUidForCharacterIdent(string ident, string serverId)
|
||||||
|
{
|
||||||
|
var exists = cachedIdentities.Any(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId);
|
||||||
|
return Task.FromResult(exists ? cachedIdentities.FirstOrDefault(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId).Key : string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Task<ServerIdentity> GetIdentForuid(string uid)
|
||||||
|
{
|
||||||
|
ServerIdentity result;
|
||||||
|
if (!cachedIdentities.TryGetValue(uid, out result))
|
||||||
|
{
|
||||||
|
result = new ServerIdentity();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetIdent(string uid, string serverId, string ident)
|
||||||
|
{
|
||||||
|
cachedIdentities[uid] = new ServerIdentity() { ServerId = serverId, CharacterIdent = ident };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RemoveIdent(string uid, string serverId)
|
||||||
|
{
|
||||||
|
if (cachedIdentities.ContainsKey(uid) && cachedIdentities[uid].ServerId == serverId)
|
||||||
|
{
|
||||||
|
cachedIdentities.TryRemove(uid, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int GetOnlineUsers(string serverId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(serverId))
|
||||||
|
return cachedIdentities.Count;
|
||||||
|
return cachedIdentities.Count(c => c.Value.ServerId == serverId);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearIdentsForServer(string serverId)
|
||||||
|
{
|
||||||
|
var serverIdentities = cachedIdentities.Where(i => i.Value.ServerId == serverId);
|
||||||
|
foreach (var identity in serverIdentities)
|
||||||
|
{
|
||||||
|
cachedIdentities.TryRemove(identity.Key, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal record ServerIdentity
|
||||||
|
{
|
||||||
|
public string ServerId { get; set; } = string.Empty;
|
||||||
|
public string CharacterIdent { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using Grpc.Core;
|
||||||
|
using MareSynchronosShared.Protos;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MareSynchronosServices.Identity;
|
||||||
|
|
||||||
|
internal class IdentityService : IdentificationService.IdentificationServiceBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<IdentityService> _logger;
|
||||||
|
private readonly IdentityHandler _handler;
|
||||||
|
|
||||||
|
public IdentityService(ILogger<IdentityService> logger, IdentityHandler handler)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<Empty> RemoveIdentForUid(RemoveIdentMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_handler.RemoveIdent(request.Uid, request.ServerId);
|
||||||
|
return Task.FromResult(new Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<Empty> SetIdentForUid(SetIdentMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_handler.SetIdent(request.Uid, request.ServerId, request.Ident);
|
||||||
|
return Task.FromResult(new Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<CharacterIdentMessage> GetIdentForUid(UidMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = await _handler.GetIdentForuid(request.Uid);
|
||||||
|
return new CharacterIdentMessage()
|
||||||
|
{
|
||||||
|
Ident = result.CharacterIdent,
|
||||||
|
ServerId = result.ServerId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<UidMessage> GetUidForCharacterIdent(CharacterIdentMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
var result = await _handler.GetUidForCharacterIdent(request.Ident, request.ServerId);
|
||||||
|
return new UidMessage()
|
||||||
|
{
|
||||||
|
Uid = result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<OnlineUserCountResponse> GetOnlineUserCount(ServerMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new OnlineUserCountResponse() { Count = _handler.GetOnlineUsers(request.ServerId) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<Empty> ClearIdentsForServer(ServerMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
_handler.ClearIdentsForServer(request.ServerId);
|
||||||
|
return Task.FromResult(new Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task<Empty> RecreateServerIdents(ServerIdentMessage request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
foreach (var identMsg in request.Idents)
|
||||||
|
{
|
||||||
|
_handler.SetIdent(identMsg.Uid, identMsg.ServerId, identMsg.Ident);
|
||||||
|
}
|
||||||
|
return Task.FromResult(new Empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MareSynchronosShared.Services;
|
using MareSynchronosServices.Identity;
|
||||||
|
|
||||||
namespace MareSynchronosServices;
|
namespace MareSynchronosServices;
|
||||||
|
|
||||||
@@ -45,29 +45,12 @@ public class Startup
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
services.AddSingleton<SecretKeyAuthenticationHandler>();
|
services.AddSingleton<SecretKeyAuthenticationHandler>();
|
||||||
|
services.AddSingleton<IdentityHandler>();
|
||||||
services.AddSingleton<CleanupService>();
|
services.AddSingleton<CleanupService>();
|
||||||
services.AddTransient(_ => Configuration);
|
services.AddTransient(_ => Configuration);
|
||||||
services.AddHostedService(provider => provider.GetService<CleanupService>());
|
services.AddHostedService(provider => provider.GetService<CleanupService>());
|
||||||
services.AddHostedService<DiscordBot>();
|
services.AddHostedService<DiscordBot>();
|
||||||
services.AddGrpc();
|
services.AddGrpc();
|
||||||
|
|
||||||
// add redis related options
|
|
||||||
var redis = Configuration.GetSection("MareSynchronos").GetValue("RedisConnectionString", string.Empty);
|
|
||||||
if (!string.IsNullOrEmpty(redis))
|
|
||||||
{
|
|
||||||
services.AddStackExchangeRedisCache(opt =>
|
|
||||||
{
|
|
||||||
opt.Configuration = redis;
|
|
||||||
opt.InstanceName = "MareSynchronosCache:";
|
|
||||||
});
|
|
||||||
services.AddSingleton<IClientIdentificationService, DistributedClientIdentificationService>();
|
|
||||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
|
|
||||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
@@ -80,6 +63,7 @@ public class Startup
|
|||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapGrpcService<AuthenticationService>();
|
endpoints.MapGrpcService<AuthenticationService>();
|
||||||
|
endpoints.MapGrpcService<IdentityService>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,8 +16,50 @@ service FileService {
|
|||||||
rpc DeleteFiles (DeleteFilesRequest) returns (Empty);
|
rpc DeleteFiles (DeleteFilesRequest) returns (Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service IdentificationService {
|
||||||
|
rpc GetOnlineUserCount (ServerMessage) returns (OnlineUserCountResponse);
|
||||||
|
rpc RemoveIdentForUid (RemoveIdentMessage) returns (Empty);
|
||||||
|
rpc SetIdentForUid (SetIdentMessage) returns (Empty);
|
||||||
|
rpc GetUidForCharacterIdent (CharacterIdentMessage) returns (UidMessage);
|
||||||
|
rpc GetIdentForUid (UidMessage) returns (CharacterIdentMessage);
|
||||||
|
rpc ClearIdentsForServer (ServerMessage) returns (Empty);
|
||||||
|
rpc RecreateServerIdents (ServerIdentMessage) returns (Empty);
|
||||||
|
}
|
||||||
|
|
||||||
message Empty { }
|
message Empty { }
|
||||||
|
|
||||||
|
message ServerIdentMessage {
|
||||||
|
repeated SetIdentMessage idents = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerMessage {
|
||||||
|
string server_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OnlineUserCountResponse {
|
||||||
|
int64 count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveIdentMessage {
|
||||||
|
string uid = 1;
|
||||||
|
string server_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SetIdentMessage {
|
||||||
|
string uid = 1;
|
||||||
|
string server_id = 2;
|
||||||
|
string ident = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CharacterIdentMessage {
|
||||||
|
string server_id = 1;
|
||||||
|
string ident = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UidMessage {
|
||||||
|
string uid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message UploadFileRequest {
|
message UploadFileRequest {
|
||||||
string hash = 1;
|
string hash = 1;
|
||||||
string uploader = 2;
|
string uploader = 2;
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using MareSynchronosShared.Metrics;
|
|
||||||
|
|
||||||
namespace MareSynchronosShared.Services;
|
|
||||||
|
|
||||||
public abstract class BaseClientIdentificationService : IClientIdentificationService
|
|
||||||
{
|
|
||||||
private readonly MareMetrics metrics;
|
|
||||||
protected ConcurrentDictionary<string, string> OnlineClients = new();
|
|
||||||
protected BaseClientIdentificationService(MareMetrics metrics)
|
|
||||||
{
|
|
||||||
this.metrics = metrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Task<int> GetOnlineUsers()
|
|
||||||
{
|
|
||||||
return Task.FromResult(OnlineClients.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string?> GetUidForCharacterIdent(string characterIdent)
|
|
||||||
{
|
|
||||||
var result = OnlineClients.SingleOrDefault(u =>
|
|
||||||
string.Compare(u.Value, characterIdent, StringComparison.InvariantCultureIgnoreCase) == 0);
|
|
||||||
return Task.FromResult(result.Equals(new KeyValuePair<string, string>()) ? null : result.Key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Task<string?> GetCharacterIdentForUid(string uid)
|
|
||||||
{
|
|
||||||
if (!OnlineClients.TryGetValue(uid, out var result))
|
|
||||||
{
|
|
||||||
return Task.FromResult((string?)null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Task MarkUserOnline(string uid, string charaIdent)
|
|
||||||
{
|
|
||||||
OnlineClients[uid] = charaIdent;
|
|
||||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Task MarkUserOffline(string uid)
|
|
||||||
{
|
|
||||||
if (OnlineClients.TryRemove(uid, out _))
|
|
||||||
{
|
|
||||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, 0);
|
|
||||||
OnlineClients = new();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
using System.Text;
|
|
||||||
using MareSynchronosShared.Metrics;
|
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using StackExchange.Redis;
|
|
||||||
|
|
||||||
namespace MareSynchronosShared.Services;
|
|
||||||
|
|
||||||
public class DistributedClientIdentificationService : BaseClientIdentificationService
|
|
||||||
{
|
|
||||||
private readonly IDistributedCache distributedCache;
|
|
||||||
private readonly ILogger<DistributedClientIdentificationService> logger;
|
|
||||||
private readonly IConfiguration configuration;
|
|
||||||
private const string RedisPrefix = "uidcache:";
|
|
||||||
|
|
||||||
public DistributedClientIdentificationService(MareMetrics metrics, IDistributedCache distributedCache, IConfiguration configuration, ILogger<DistributedClientIdentificationService> logger) : base(metrics)
|
|
||||||
{
|
|
||||||
this.distributedCache = distributedCache;
|
|
||||||
this.logger = logger;
|
|
||||||
this.configuration = configuration.GetSection("MareSynchronos");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<int> GetOnlineUsers()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var redis = configuration.GetValue<string>("RedisConnectionString");
|
|
||||||
var conn = await ConnectionMultiplexer.ConnectAsync(redis).ConfigureAwait(false);
|
|
||||||
var endpoint = conn.GetEndPoints().First();
|
|
||||||
return await conn.GetServer(endpoint).KeysAsync(pattern: "*" + RedisPrefix + "*").CountAsync().ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, "Error during GetOnlineUsers");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<string?> GetCharacterIdentForUid(string uid)
|
|
||||||
{
|
|
||||||
var localIdent = await base.GetCharacterIdentForUid(uid).ConfigureAwait(false);
|
|
||||||
if (localIdent != null) return localIdent;
|
|
||||||
var cachedIdent = await distributedCache.GetStringAsync(RedisPrefix + uid).ConfigureAwait(false);
|
|
||||||
return cachedIdent ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task MarkUserOffline(string uid)
|
|
||||||
{
|
|
||||||
await base.MarkUserOffline(uid).ConfigureAwait(false);
|
|
||||||
await distributedCache.RemoveAsync(RedisPrefix + uid).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task MarkUserOnline(string uid, string charaIdent)
|
|
||||||
{
|
|
||||||
await base.MarkUserOnline(uid, charaIdent).ConfigureAwait(false);
|
|
||||||
await distributedCache.SetAsync(RedisPrefix + uid, Encoding.UTF8.GetBytes(charaIdent), new DistributedCacheEntryOptions()
|
|
||||||
{
|
|
||||||
AbsoluteExpiration = DateTime.Now.AddDays(7)
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
foreach (var uid in OnlineClients)
|
|
||||||
{
|
|
||||||
distributedCache.Remove(RedisPrefix + uid.Key);
|
|
||||||
}
|
|
||||||
return base.StopAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace MareSynchronosShared.Services;
|
|
||||||
|
|
||||||
public interface IClientIdentificationService : IHostedService
|
|
||||||
{
|
|
||||||
Task<int> GetOnlineUsers();
|
|
||||||
Task<string?> GetUidForCharacterIdent(string characterIdent);
|
|
||||||
Task<string?> GetCharacterIdentForUid(string uid);
|
|
||||||
Task MarkUserOnline(string uid, string charaIdent);
|
|
||||||
Task MarkUserOffline(string uid);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using MareSynchronosShared.Metrics;
|
|
||||||
|
|
||||||
namespace MareSynchronosShared.Services;
|
|
||||||
|
|
||||||
public class LocalClientIdentificationService : BaseClientIdentificationService
|
|
||||||
{
|
|
||||||
public LocalClientIdentificationService(MareMetrics metrics) : base(metrics)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user