From 692c71c0c5f13bfdceba425392dce5e172c10d3f Mon Sep 17 00:00:00 2001 From: NotBlue <64601123+NotBlue-Dev@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:29:54 +0100 Subject: [PATCH] Add public HTTP API endpoints for centralized app (#22) --- .../Forms/Controls/ServerInfoControl.cs | 14 +++- EchoRelay.App/Forms/MainWindow.cs | 9 +++ EchoRelay.Cli/Program.cs | 78 ++++++++++++++++--- EchoRelay.Core/Monitoring/ApiClient.cs | 42 ++++++++++ EchoRelay.Core/Monitoring/ApiManager.cs | 41 ++++++++++ EchoRelay.Core/Monitoring/GameServer.cs | 63 +++++++++++++++ EchoRelay.Core/Monitoring/GameServerObject.cs | 25 ++++++ EchoRelay.Core/Monitoring/PeerStats.cs | 30 +++++++ EchoRelay.Core/Monitoring/PeerStatsObject.cs | 23 ++++++ EchoRelay.Core/Monitoring/Server.cs | 63 +++++++++++++++ EchoRelay.Core/Monitoring/ServerObject.cs | 28 +++++++ EchoRelay.Core/Server/Server.cs | 36 ++++++++- EchoRelay.Core/Server/ServerSettings.cs | 8 +- .../Services/ServerDB/GameServerRegistry.cs | 18 +++++ .../Services/ServerDB/RegisteredGameServer.cs | 45 ++++++++++- 15 files changed, 509 insertions(+), 14 deletions(-) create mode 100644 EchoRelay.Core/Monitoring/ApiClient.cs create mode 100644 EchoRelay.Core/Monitoring/ApiManager.cs create mode 100644 EchoRelay.Core/Monitoring/GameServer.cs create mode 100644 EchoRelay.Core/Monitoring/GameServerObject.cs create mode 100644 EchoRelay.Core/Monitoring/PeerStats.cs create mode 100644 EchoRelay.Core/Monitoring/PeerStatsObject.cs create mode 100644 EchoRelay.Core/Monitoring/Server.cs create mode 100644 EchoRelay.Core/Monitoring/ServerObject.cs diff --git a/EchoRelay.App/Forms/Controls/ServerInfoControl.cs b/EchoRelay.App/Forms/Controls/ServerInfoControl.cs index 2d16b3f..7ccd0df 100644 --- a/EchoRelay.App/Forms/Controls/ServerInfoControl.cs +++ b/EchoRelay.App/Forms/Controls/ServerInfoControl.cs @@ -1,11 +1,13 @@ -using EchoRelay.Core.Server; +using EchoRelay.Core.Monitoring; using EchoRelay.Core.Utils; using Newtonsoft.Json; +using Server = EchoRelay.Core.Server.Server; namespace EchoRelay.App.Forms.Controls { public partial class ServerInfoControl : UserControl { + ApiManager _apiManager = ApiManager.Instance; public ServerInfoControl() { InitializeComponent(); @@ -36,6 +38,16 @@ public void UpdateServerInfo(Server? server, bool updateServiceConfig) rtbGeneratedServiceConfig.Text = ""; } } + + + _apiManager.peerStatsObject.ServerIp = server?.PublicIPAddress?.ToString() ?? "localhost"; + _apiManager.peerStatsObject.Login = server?.LoginService.Peers.Count ?? 0; + _apiManager.peerStatsObject.Matching = server?.MatchingService.Peers.Count ?? 0; + _apiManager.peerStatsObject.Config = server?.ConfigService.Peers.Count ?? 0; + _apiManager.peerStatsObject.Transaction = server?.TransactionService.Peers.Count ?? 0; + _apiManager.peerStatsObject.ServerDb = server?.ServerDBService.Peers.Count ?? 0; + Task.Run(() => _apiManager.PeerStats.EditPeerStats(_apiManager.peerStatsObject, server?.PublicIPAddress?.ToString() ?? "localhost")); + } private void btnCopyServiceConfig_Click(object sender, EventArgs e) diff --git a/EchoRelay.App/Forms/MainWindow.cs b/EchoRelay.App/Forms/MainWindow.cs index 88ad27c..66bf803 100644 --- a/EchoRelay.App/Forms/MainWindow.cs +++ b/EchoRelay.App/Forms/MainWindow.cs @@ -13,6 +13,8 @@ using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; +using EchoRelay.Core.Monitoring; +using Server = EchoRelay.Core.Server.Server; namespace EchoRelay { @@ -36,6 +38,8 @@ public partial class MainWindow : Form /// The websocket server used to power central game services. /// public Server Server { get; set; } + + public ApiManager apiManager { get; set; } /// /// The UI editors for different storage resources. @@ -130,6 +134,8 @@ public MainWindow() Server.ServerDBService.Registry.OnGameServerRegistered += Registry_OnGameServerRegistered; Server.ServerDBService.Registry.OnGameServerUnregistered += Registry_OnGameServerUnregistered; Server.ServerDBService.OnGameServerRegistrationFailure += ServerDBService_OnGameServerRegistrationFailure; ; + + apiManager = ApiManager.Instance; } #endregion @@ -360,6 +366,9 @@ private void Registry_OnGameServerRegistered(EchoRelay.Core.Server.Services.Serv // Update server count on the game server tab. tabGameServers.Text = $"Game Servers ({gameServersControl.GameServerCount})"; + apiManager.peerStatsObject.GameServers = gameServersControl.GameServerCount; + Task.Run(() => apiManager.PeerStats.EditPeerStats(apiManager.peerStatsObject, Server.PublicIPAddress?.ToString() ?? "localhost")); + AppendLogText($"[{gameServer.Peer.Service.Name}] client({gameServer.Peer.Address}:{gameServer.Peer.Port}) registered game server (server_id={gameServer.ServerId}, region_symbol={gameServer.RegionSymbol}, version_lock={gameServer.VersionLock}, endpoint=<{gameServer.ExternalAddress}:{gameServer.Port}>)\n"); }); } diff --git a/EchoRelay.Cli/Program.cs b/EchoRelay.Cli/Program.cs index 42a6900..52456e3 100644 --- a/EchoRelay.Cli/Program.cs +++ b/EchoRelay.Cli/Program.cs @@ -8,6 +8,9 @@ using Microsoft.AspNetCore.Components.Web; using Newtonsoft.Json; using System.Reflection; +using System.Runtime.InteropServices; +using EchoRelay.Core.Monitoring; +using Server = EchoRelay.Core.Server.Server; namespace EchoRelay.Cli { @@ -20,11 +23,13 @@ class Program /// /// The instance of the server hosting central services. /// - private static Server Server; + private static Server? Server; /// /// The update timer used to trigger a peer stats update on a given interval. /// private static System.Timers.Timer? peerStatsUpdateTimer; + + private static ApiManager? apiManager; /// /// The time that the server was started. /// @@ -33,6 +38,22 @@ class Program /// A mutex lock object to be used when printing to console, to avoid color collisions. /// private static object _printLock = new object(); + + [DllImport("Kernel32")] + private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add); + + private delegate bool EventHandler(CtrlType sig); + private static EventHandler? consoleCloseHandler; + + // Enum to represent different CtrlTypes + private enum CtrlType + { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6 + } /// /// The CLI argument options for the application. @@ -139,6 +160,8 @@ static void Main(string[] args) ) ); + apiManager = ApiManager.Instance; + // Set up all event handlers. Server.OnServerStarted += Server_OnServerStarted; Server.OnServerStopped += Server_OnServerStopped; @@ -149,6 +172,10 @@ static void Main(string[] args) Server.ServerDBService.Registry.OnGameServerRegistered += Registry_OnGameServerRegistered; Server.ServerDBService.Registry.OnGameServerUnregistered += Registry_OnGameServerUnregistered; Server.ServerDBService.OnGameServerRegistrationFailure += ServerDBService_OnGameServerRegistrationFailure; + + // Set up the event handler for the console close event + consoleCloseHandler += new EventHandler(ConsoleCloseHandler); + SetConsoleCtrlHandler(consoleCloseHandler, true); // Set up all verbose event handlers. if (options.Verbose) @@ -187,6 +214,8 @@ private static void Server_OnServerStarted(Server server) } } + updateServerInfo(); + // Start the peer stats update timer startedTime = DateTime.UtcNow; peerStatsUpdateTimer = new System.Timers.Timer(Options!.StatsUpdateInterval); @@ -197,14 +226,14 @@ private static void Server_OnServerStarted(Server server) private static void PeerStatsUpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { Info($"[PEERSTATS] " + - $"elapsed: {(DateTime.UtcNow - startedTime)}, " + - $"gameservers: {Server.ServerDBService.Registry.RegisteredGameServers.Count}, " + - $"login: {Server.LoginService.Peers.Count}, " + - $"config: {Server.ConfigService.Peers.Count}, " + - $"matching: {Server.MatchingService.Peers.Count}, " + - $"serverdb: {Server.ServerDBService.Peers.Count}, " + - $"transaction: {Server.TransactionService.Peers.Count}" - ); + $"elapsed: {(DateTime.UtcNow - startedTime)}, " + + $"gameservers: {Server.ServerDBService.Registry.RegisteredGameServers.Count}, " + + $"login: {Server.LoginService.Peers.Count}, " + + $"config: {Server.ConfigService.Peers.Count}, " + + $"matching: {Server.MatchingService.Peers.Count}, " + + $"serverdb: {Server.ServerDBService.Peers.Count}, " + + $"transaction: {Server.TransactionService.Peers.Count}" + ); } private static void Server_OnServerStopped(Server server) @@ -214,6 +243,8 @@ private static void Server_OnServerStopped(Server server) // Print our server stopped message Info("[SERVER] Server stopped"); + + updateServerInfo(); } private static void Server_OnAuthorizationResult(Server server, System.Net.IPEndPoint client, bool authorized) @@ -221,13 +252,27 @@ private static void Server_OnAuthorizationResult(Server server, System.Net.IPEnd if(!authorized) Error($"[SERVER] client({client.Address}:{client.Port}) failed authorization"); } + + private static void updateServerInfo() + { + apiManager.peerStatsObject.ServerIp = Server?.PublicIPAddress?.ToString() ?? "localhost"; + apiManager.peerStatsObject.Login = Server?.LoginService.Peers.Count; + apiManager.peerStatsObject.Matching = Server?.MatchingService.Peers.Count; + apiManager.peerStatsObject.Config = Server?.ConfigService.Peers.Count; + apiManager.peerStatsObject.Transaction = Server?.TransactionService.Peers.Count; + apiManager.peerStatsObject.ServerDb = Server?.ServerDBService.Peers.Count; + apiManager.peerStatsObject.GameServers = Server?.ServerDBService.Registry.RegisteredGameServers.Count; + Task.Run(() => apiManager.PeerStats.EditPeerStats(apiManager.peerStatsObject, apiManager.peerStatsObject.ServerIp)); + } private static void Server_OnServicePeerConnected(Core.Server.Services.Service service, Core.Server.Services.Peer peer) { Info($"[{service.Name}] client({peer.Address}:{peer.Port}) connected"); + updateServerInfo(); } private static void Server_OnServicePeerDisconnected(Core.Server.Services.Service service, Core.Server.Services.Peer peer) { Info($"[{service.Name}] client({peer.Address}:{peer.Port}) disconnected"); + updateServerInfo(); } private static void Server_OnServicePeerAuthenticated(Core.Server.Services.Service service, Core.Server.Services.Peer peer, Core.Game.XPlatformId userId) { @@ -237,11 +282,14 @@ private static void Server_OnServicePeerAuthenticated(Core.Server.Services.Servi private static void Registry_OnGameServerRegistered(Core.Server.Services.ServerDB.RegisteredGameServer gameServer) { Info($"[{gameServer.Peer.Service.Name}] client({gameServer.Peer.Address}:{gameServer.Peer.Port}) registered game server (server_id={gameServer.ServerId}, region_symbol={gameServer.RegionSymbol}, version_lock={gameServer.VersionLock}, endpoint=<{gameServer.ExternalAddress}:{gameServer.Port}>)"); + updateServerInfo(); } private static void Registry_OnGameServerUnregistered(Core.Server.Services.ServerDB.RegisteredGameServer gameServer) { Info($"[{gameServer.Peer.Service.Name}] client({gameServer.Peer.Address}:{gameServer.Peer.Port}) unregistered game server (server_id={gameServer.ServerId}, region_symbol={gameServer.RegionSymbol}, version_lock={gameServer.VersionLock}, endpoint=<{gameServer.ExternalAddress}:{gameServer.Port}>)"); + updateServerInfo(); + } private static void ServerDBService_OnGameServerRegistrationFailure(Peer peer, Core.Server.Messages.ServerDB.ERGameServerRegistrationRequest registrationRequest, string failureMessage) { @@ -310,5 +358,17 @@ private static void Debug(string msg) Console.ResetColor(); } } + + // Handler for the console close event + private static bool ConsoleCloseHandler(CtrlType sig) + { + Console.WriteLine("Console is closing. Performing cleanup..."); + Server?.Stop(); + + Thread.Sleep(1500); + + // Allow the program to exit + return true; + } } } \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/ApiClient.cs b/EchoRelay.Core/Monitoring/ApiClient.cs new file mode 100644 index 0000000..f071e0e --- /dev/null +++ b/EchoRelay.Core/Monitoring/ApiClient.cs @@ -0,0 +1,42 @@ +using System.Security.Cryptography; +using System.Text; + +namespace EchoRelay.Core.Monitoring; + +//TO DO : Encrypt data +public class ApiClient +{ + private readonly HttpClient _httpClient; + + public ApiClient(string baseUrl) + { + _httpClient = new HttpClient + { + BaseAddress = new Uri(baseUrl) + }; + } + + //TODO encrypt data to avoid people to send fake data + public async Task PostMonitoringData(string endpoint, string data) + { + HttpContent content = new StringContent(data, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await _httpClient.PostAsync(endpoint, content); + + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadAsStringAsync(); + } + throw new Exception($"API request failed with status code: {response.StatusCode}"); + } + + public async Task DeleteMonitoringData(string endpoint) + { + HttpResponseMessage response = await _httpClient.DeleteAsync(endpoint); + + if (!response.IsSuccessStatusCode) + { + throw new Exception($"DELETE request failed with status code: {response.StatusCode}"); + } + } + +} \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/ApiManager.cs b/EchoRelay.Core/Monitoring/ApiManager.cs new file mode 100644 index 0000000..9b45ca5 --- /dev/null +++ b/EchoRelay.Core/Monitoring/ApiManager.cs @@ -0,0 +1,41 @@ +using System.Security.Cryptography; + +namespace EchoRelay.Core.Monitoring; + +public class ApiManager +{ + + /// + /// Uri to the monitoring API + /// + public string URI { get; } = "http://localhost:3000/api/"; + //public string URI { get; } = "http://51.75.140.182:3000/api/"; + + public PeerStatsObject peerStatsObject; + /// + /// Public key to encrypt data + /// + public ApiClient Monitoring { get; } + + public Server Server { get; } + + public GameServer GameServer { get; } + public PeerStats PeerStats { get; } + + private static ApiManager instance = new ApiManager(); + + private ApiManager() + { + Monitoring = new ApiClient(URI); + GameServer = new GameServer(Monitoring); + PeerStats = new PeerStats(Monitoring); + Server = new Server(Monitoring); + peerStatsObject = new PeerStatsObject(); + } + + // Method to get the singleton instance + public static ApiManager Instance + { + get { return instance; } + } +} \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/GameServer.cs b/EchoRelay.Core/Monitoring/GameServer.cs new file mode 100644 index 0000000..45fb1f2 --- /dev/null +++ b/EchoRelay.Core/Monitoring/GameServer.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; + +namespace EchoRelay.Core.Monitoring; + +public class GameServer +{ + private readonly ApiClient _apiClient; + + public GameServer(ApiClient apiClient) + { + _apiClient = apiClient; + } + + public async Task DeleteGameServerAsync(string sessionId) + { + // Define the endpoint for deleting a game server with the sessionID + string endpoint = $"deleteGameServer/{sessionId}"; + + try + { + await _apiClient.DeleteMonitoringData(endpoint); + Console.WriteLine($"Game server with session ID {sessionId} deleted successfully from monitoring."); + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting the game server from monitoring: {ex.Message}"); + } + } + + public async Task AddGameServerAsync(GameServerObject jsonObject) + { + // Create a StringContent with the JSON data and set the content type + string jsonData = JsonConvert.SerializeObject(jsonObject); + string endpoint = "addGameServer/"; + + try + { + await _apiClient.PostMonitoringData(endpoint, jsonData); + Console.WriteLine($"Game server successfully added to monitoring."); + } + catch (Exception ex) + { + Console.WriteLine($"Error adding the game server from monitoring: {ex.Message}"); + } + } + + public async Task EditGameServer(GameServerObject jsonObject, string gameServerID) + { + // Create a StringContent with the JSON data and set the content type + string jsonData = JsonConvert.SerializeObject(jsonObject); + string endpoint = $"updateGameServer/{gameServerID}"; + + try + { + await _apiClient.PostMonitoringData(endpoint, jsonData); + Console.WriteLine($"Game server successfully edited in monitoring."); + } + catch (Exception ex) + { + Console.WriteLine($"Error editing the game server in monitoring: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/GameServerObject.cs b/EchoRelay.Core/Monitoring/GameServerObject.cs new file mode 100644 index 0000000..a341780 --- /dev/null +++ b/EchoRelay.Core/Monitoring/GameServerObject.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace EchoRelay.Core.Monitoring; + +public class GameServerObject +{ + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "serverIP")] + public string? ServerIp { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "region")] + public string? Region { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "level")] + public string? Level { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "gameMode")] + public string? GameMode { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "playerCount")] + public int PlayerCount { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "assigned")] + public bool Assigned { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "sessionID")] + public string? SessionId { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "gameServerID")] + public ulong GameServerId { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "public")] + public bool @Public { get; set; } +} \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/PeerStats.cs b/EchoRelay.Core/Monitoring/PeerStats.cs new file mode 100644 index 0000000..bac7c8c --- /dev/null +++ b/EchoRelay.Core/Monitoring/PeerStats.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace EchoRelay.Core.Monitoring; + +public class PeerStats +{ + private readonly ApiClient _apiClient; + + public PeerStats(ApiClient apiClient) + { + _apiClient = apiClient; + } + + public async Task EditPeerStats(PeerStatsObject jsonObject, string server) + { + // Create a StringContent with the JSON data and set the content type + string jsonData = JsonConvert.SerializeObject(jsonObject); + string endpoint = $"updatePeerStats/{server}"; + + try + { + await _apiClient.PostMonitoringData(endpoint, jsonData); + Console.WriteLine($"Peer stats successfully edited in monitoring."); + } + catch (Exception ex) + { + Console.WriteLine($"Error editing the Peer stats in monitoring: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/PeerStatsObject.cs b/EchoRelay.Core/Monitoring/PeerStatsObject.cs new file mode 100644 index 0000000..c71817f --- /dev/null +++ b/EchoRelay.Core/Monitoring/PeerStatsObject.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace EchoRelay.Core.Monitoring; + +public class PeerStatsObject +{ + [JsonProperty(PropertyName = "serverIP")] + public string? ServerIp { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "gameServers")] + public int? GameServers { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "login")] + public int? Login { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "matching")] + public int? Matching { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "config")] + public int? Config { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "transaction")] + public int? Transaction { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "serverdb")] + public int? ServerDb { get; set; } +} \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/Server.cs b/EchoRelay.Core/Monitoring/Server.cs new file mode 100644 index 0000000..01983a7 --- /dev/null +++ b/EchoRelay.Core/Monitoring/Server.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json; + +namespace EchoRelay.Core.Monitoring; + +public class Server +{ + private readonly ApiClient _apiClient; + + public Server(ApiClient apiClient) + { + _apiClient = apiClient; + } + + public async Task DeleteServerAsync(string server) + { + // Define the endpoint for deleting a game server with the server IP + string endpoint = $"deleteServer/{server}"; + + try + { + await _apiClient.DeleteMonitoringData(endpoint); + Console.WriteLine($"Server {server} deleted successfully from monitoring."); + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting the server from monitoring: {ex.Message}"); + } + } + + public async Task AddServerAsync(ServerObject jsonObject) + { + // Create a StringContent with the JSON data and set the content type + string jsonData = JsonConvert.SerializeObject(jsonObject); + string endpoint = "addServer/"; + + try + { + await _apiClient.PostMonitoringData(endpoint, jsonData); + Console.WriteLine($"Server successfully added to monitoring."); + } + catch (Exception ex) + { + Console.WriteLine($"Error adding the server to monitoring: {ex.Message}"); + } + } + + public async Task EditServer(ServerObject jsonObject, string server) + { + // Create a StringContent with the JSON data and set the content type + string jsonData = JsonConvert.SerializeObject(jsonObject); + string endpoint = $"updateServer/{server}"; + + try + { + await _apiClient.PostMonitoringData(endpoint, jsonData); + Console.WriteLine($"Server successfully edited in monitoring."); + } + catch (Exception ex) + { + Console.WriteLine($"Error editing the game server in monitoring: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/EchoRelay.Core/Monitoring/ServerObject.cs b/EchoRelay.Core/Monitoring/ServerObject.cs new file mode 100644 index 0000000..d587bb8 --- /dev/null +++ b/EchoRelay.Core/Monitoring/ServerObject.cs @@ -0,0 +1,28 @@ +using EchoRelay.Core.Game; +using Newtonsoft.Json; + +namespace EchoRelay.Core.Monitoring; + +public class ServerObject +{ + [JsonProperty(PropertyName = "ip")] + public string Ip { get; set; } = ""; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "apiservice_host")] + public string? ApiServiceHost { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "configservice_host")] + public string? ConfigServiceHost { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "loginservice_host")] + public string? LoginServiceHost { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "matchingservice_host")] + public string? MatchingServiceHost { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "serverdb_host")] + public string? ServerDbHost { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "transactionservice_host")] + public string? TransactionServiceHost { get; set; } + + [JsonProperty(PropertyName = "publisher_lock")] + public string PublisherLock { get; set; } = "rad15_live"; + + [JsonProperty(PropertyName = "online")] + public bool Online { get; set; } = false; +} \ No newline at end of file diff --git a/EchoRelay.Core/Server/Server.cs b/EchoRelay.Core/Server/Server.cs index 87b931d..ed4fa41 100644 --- a/EchoRelay.Core/Server/Server.cs +++ b/EchoRelay.Core/Server/Server.cs @@ -13,6 +13,7 @@ using System.Collections.ObjectModel; using System.Net; using System.Net.WebSockets; +using EchoRelay.Core.Monitoring; using static EchoRelay.Core.Server.Services.Service; namespace EchoRelay.Core.Server @@ -36,6 +37,11 @@ public class Server /// The settings for the server to operate under. /// public ServerSettings Settings { get; private set; } + /// + /// The API manager for monitoring + /// + public ApiManager? apiManager { get; private set; } + /// /// The persistent storage layer for the server. /// @@ -174,6 +180,9 @@ public Server(ServerStorage storage, ServerSettings settings) { Settings.ServerDBServicePath.ToLower(), ServerDBService }, { Settings.TransactionServicePath.ToLower(), TransactionService }, }.AsReadOnly(); + + apiManager = ApiManager.Instance; + } #endregion @@ -186,6 +195,7 @@ public Server(ServerStorage storage, ServerSettings settings) /// An exception thrown if the server is already started when this method is called. public async Task Start(CancellationTokenSource? cancellationTokenSource = null) { + Console.WriteLine("Starting server..."); // If we are running already, throw an exception. if (Running) { @@ -212,6 +222,18 @@ public async Task Start(CancellationTokenSource? cancellationTokenSource = null) // Fire our started event OnServerStarted?.Invoke(this); + ServerObject server = new ServerObject(); + ServiceConfig serviceConfig = Settings.GenerateServiceConfig(PublicIPAddress?.ToString() ?? "localhost", apiKey:false); + server.ApiServiceHost = serviceConfig.ApiServiceHost; + server.LoginServiceHost = serviceConfig.LoginServiceHost; + server.ConfigServiceHost = serviceConfig.ConfigServiceHost; + server.MatchingServiceHost = serviceConfig.MatchingServiceHost; + server.ServerDbHost = serviceConfig.ServerDBServiceHost; + server.TransactionServiceHost = serviceConfig.TransactionServiceHost; + server.Ip = PublicIPAddress?.ToString() ?? "localhost"; + server.Online = true; + _ = Task.Run(() => apiManager?.Server.EditServer(server, server.Ip)); + // Enter a loop to accept new web socket connections. try { @@ -297,8 +319,20 @@ public async Task Start(CancellationTokenSource? cancellationTokenSource = null) /// Stops the server and its underlying services. /// Note: Servers are run in another task. This method may return before the server has stopped. /// - public void Stop() + public async void Stop() { + ServerObject server = new ServerObject(); + server.Ip = PublicIPAddress?.ToString() ?? "localhost"; + server.Online = false; + _ = Task.Run(() => apiManager?.Server.EditServer(server, server.Ip)); + apiManager.peerStatsObject.ServerIp = PublicIPAddress?.ToString(); + apiManager.peerStatsObject.Login = 0; + apiManager.peerStatsObject.Matching = 0; + apiManager.peerStatsObject.Config = 0; + apiManager.peerStatsObject.Transaction = 0; + apiManager.peerStatsObject.ServerDb = 0; + await apiManager.PeerStats.EditPeerStats(apiManager.peerStatsObject, + PublicIPAddress?.ToString() ?? "localhost"); // Cancel any cancellation token we have now. _cancellationTokenSource?.Cancel(); } diff --git a/EchoRelay.Core/Server/ServerSettings.cs b/EchoRelay.Core/Server/ServerSettings.cs index 85961a3..370fd26 100644 --- a/EchoRelay.Core/Server/ServerSettings.cs +++ b/EchoRelay.Core/Server/ServerSettings.cs @@ -111,15 +111,19 @@ public ServerSettings(ushort port = 777, string apiServicePath = "/api", string /// The address or domain to use for the base URL for all host endpoints. /// Indicates whether sensitive gameserver-only fields should be included in the config. /// The publisher/environment lock to generate with. + /// Used to generate a config without API key + /// Returns the generated . - public ServiceConfig GenerateServiceConfig(string address, bool serverConfig = true, string publisherLock = "rad15_live", string? serverPlugin = null) + public ServiceConfig GenerateServiceConfig(string address, bool serverConfig = true, string publisherLock = "rad15_live", string? serverPlugin = null, bool? apiKey = null) { // Obtain our base host string webSocketHost = $"ws://{address}:{Port}"; string httpHost = $"http://{address}:{Port}"; + string serverDBHostNoKey; // Construct our ServerDB path string serverDBHost = webSocketHost + ServerDBServicePath; + serverDBHostNoKey = serverDBHost; if (ServerDBApiKey != null) { serverDBHost += $"?api_key={HttpUtility.UrlEncode(ServerDBApiKey)}"; @@ -131,7 +135,7 @@ public ServiceConfig GenerateServiceConfig(string address, bool serverConfig = t configServiceHost: webSocketHost + ConfigServicePath, loginServiceHost: webSocketHost + LoginServicePath + $"?auth=AccountPassword&displayname=AccountName", matchingServiceHost: webSocketHost + MatchingServicePath, - serverdbServiceHost: serverConfig ? serverDBHost : null, + serverdbServiceHost: apiKey == null ? (serverConfig ? serverDBHost : null) : serverDBHostNoKey, transactionServiceHost: webSocketHost + TransactionServicePath, publisherLock: publisherLock, serverPlugin: serverPlugin diff --git a/EchoRelay.Core/Server/Services/ServerDB/GameServerRegistry.cs b/EchoRelay.Core/Server/Services/ServerDB/GameServerRegistry.cs index 3899a3d..5ea6d32 100644 --- a/EchoRelay.Core/Server/Services/ServerDB/GameServerRegistry.cs +++ b/EchoRelay.Core/Server/Services/ServerDB/GameServerRegistry.cs @@ -2,6 +2,7 @@ using EchoRelay.Core.Server.Messages.ServerDB; using EchoRelay.Core.Utils; using System.Collections.Concurrent; +using EchoRelay.Core.Monitoring; using static EchoRelay.Core.Server.Messages.ServerDB.ERGameServerStartSession; namespace EchoRelay.Core.Server.Services.ServerDB @@ -28,6 +29,8 @@ public class GameServerRegistry /// Event of a game server being unregistered. /// public event GameServerRegistrationChangedEventHandler? OnGameServerUnregistered; + + public ApiManager apiManager; #endregion #region Constructor @@ -35,6 +38,7 @@ public GameServerRegistry() { RegisteredGameServers = new ConcurrentDictionary(); RegisteredGameServersBySessionId = new ConcurrentDictionary(); + apiManager = ApiManager.Instance; } #endregion @@ -43,9 +47,22 @@ public RegisteredGameServer AddGameServer(RegisteredGameServer registeredGameSer { // Add the game server to our lookup RegisteredGameServers[registeredGameServer.ServerId] = registeredGameServer; + // Fire the relevant event for the game server being registered. OnGameServerRegistered?.Invoke(registeredGameServer); + + GameServerObject gameServerObject = new GameServerObject(); + gameServerObject.ServerIp = registeredGameServer.Server.PublicIPAddress?.ToString(); + gameServerObject.Region = ""; + gameServerObject.SessionId = registeredGameServer.SessionId.ToString(); + gameServerObject.GameServerId = registeredGameServer.ServerId; + gameServerObject.Assigned = false; + gameServerObject.GameMode = ""; + gameServerObject.Public = registeredGameServer.SessionLobbyType == LobbyType.Public; + gameServerObject.Level = ""; + gameServerObject.PlayerCount = registeredGameServer.SessionPlayerCount; + Task.Run(() => apiManager.GameServer.AddGameServerAsync(gameServerObject)); return registeredGameServer; } @@ -63,6 +80,7 @@ public RegisteredGameServer AddGameServer(RegisteredGameServer registeredGameSer public void RemoveGameServer(RegisteredGameServer registeredGameServer) { + Task.Run(() => apiManager.GameServer.DeleteGameServerAsync(registeredGameServer.ServerId.ToString())); RemoveGameServer(registeredGameServer.ServerId); } public void RemoveGameServer(ulong serverId) diff --git a/EchoRelay.Core/Server/Services/ServerDB/RegisteredGameServer.cs b/EchoRelay.Core/Server/Services/ServerDB/RegisteredGameServer.cs index 83b23f2..410f1ac 100644 --- a/EchoRelay.Core/Server/Services/ServerDB/RegisteredGameServer.cs +++ b/EchoRelay.Core/Server/Services/ServerDB/RegisteredGameServer.cs @@ -7,6 +7,7 @@ using System.Net; using System.Reflection; using System.Security.Cryptography; +using EchoRelay.Core.Monitoring; namespace EchoRelay.Core.Server.Services.ServerDB { @@ -22,6 +23,8 @@ public class RegisteredGameServer /// The parent which the is registered to. /// private GameServerRegistry Registry { get; } + + public ApiManager apiManager { get; } /// /// The registration request provided by the game server initially. @@ -203,6 +206,7 @@ public RegisteredGameServer(GameServerRegistry registry, Peer peer, ERGameServer SessionPlayerLimits = GameTypePlayerLimits.DefaultLimits; _playerSessions = new Dictionary(); _accessLock = new AsyncLock(); + apiManager = ApiManager.Instance; } #endregion @@ -288,6 +292,18 @@ private async Task StartSessionInternal(XPlatformId requester, ERGameServerStart // Send the start session message to the game server. await Peer.Send(new ERGameServerStartSession(SessionId.Value, SessionChannel.Value, (byte)SessionPlayerLimits.TotalPlayerLimit, SessionLobbyType, mergedSessionSettings, entrantDescriptors.ToArray())); + GameServerObject gameServerObject = new GameServerObject(); + gameServerObject.ServerIp = Server.PublicIPAddress?.ToString(); + gameServerObject.Region = Server.SymbolCache.GetName(RegionSymbol); + gameServerObject.SessionId = SessionId.ToString(); + gameServerObject.Assigned = true; + gameServerObject.GameServerId = ServerId; + gameServerObject.GameMode = Server.SymbolCache.GetName(SessionGameTypeSymbol.Value); + gameServerObject.Public = SessionLobbyType == ERGameServerStartSession.LobbyType.Public; + gameServerObject.Level = Server.SymbolCache.GetName(SessionLevelSymbol.Value); + gameServerObject.PlayerCount = SessionPlayerCount; + _ = Task.Run(() => apiManager.GameServer.EditGameServer(gameServerObject, ServerId.ToString())); + // Add the new session id to the parent registry's lookup. Registry.RegisteredGameServersBySessionId[SessionId.Value] = this; } @@ -512,7 +528,15 @@ await _accessLock.ExecuteLocked(async () => // Fire the event for players being added if(addedPlayersInfo.Length > 0) + { OnPlayersAdded?.Invoke(this, addedPlayersInfo); + GameServerObject gameServerObject = new GameServerObject(); + gameServerObject.ServerIp = Server.PublicIPAddress?.ToString(); + gameServerObject.Assigned = true; + gameServerObject.PlayerCount = SessionPlayerCount; + _ = Task.Run(() => apiManager.GameServer.EditGameServer(gameServerObject, ServerId.ToString())); + + } } public async Task KickPlayer(Guid playerSession) @@ -544,7 +568,12 @@ await _accessLock.ExecuteLocked(() => }); // Fire the event for a player being removed - OnPlayerRemoved?.Invoke(this, playerSession, peer); + OnPlayerRemoved?.Invoke(this, playerSession, peer); + GameServerObject gameServerObject = new GameServerObject(); + gameServerObject.ServerIp = Server.PublicIPAddress?.ToString(); + gameServerObject.Assigned = true; + gameServerObject.PlayerCount = SessionPlayerCount; + _ = Task.Run(() => apiManager.GameServer.EditGameServer(gameServerObject, ServerId.ToString())); } public async Task EndSession() @@ -566,6 +595,20 @@ await _accessLock.ExecuteLocked(() => { // Fire the event for the session ending. OnSessionStateChanged?.Invoke(this); + + GameServerObject gameServerObject = new GameServerObject(); + gameServerObject.ServerIp = Server.PublicIPAddress?.ToString(); + gameServerObject.Region = RegionSymbol.ToString(); + gameServerObject.SessionId = ""; + gameServerObject.Assigned = false; + gameServerObject.GameServerId = ServerId; + gameServerObject.GameMode = ""; + gameServerObject.Public = SessionLobbyType == ERGameServerStartSession.LobbyType.Public; + gameServerObject.Level = ""; + gameServerObject.PlayerCount = SessionPlayerCount; + + _ = Task.Run(() => apiManager.GameServer.EditGameServer(gameServerObject, ServerId.ToString())); + } #endregion }