diff --git a/Moonlight/App/Services/CleanupService.cs b/Moonlight/App/Services/CleanupService.cs index 692ddf7b..42e046ce 100644 --- a/Moonlight/App/Services/CleanupService.cs +++ b/Moonlight/App/Services/CleanupService.cs @@ -13,353 +13,204 @@ namespace Moonlight.App.Services; public class CleanupService { + #region Stats public DateTime StartedAt { get; private set; } public DateTime CompletedAt { get; private set; } public int ServersCleaned { get; private set; } public int CleanupsPerformed { get; private set; } public int ServersRunning { get; private set; } public bool IsRunning { get; private set; } - public bool Activated { get; set; } - public int PercentProgress { get; private set; } = 100; - public string Status { get; private set; } = "N/A"; - - private Task PerformTask; - private readonly ConfigService ConfigService; - private readonly IServiceScopeFactory ServiceScopeFactory; - - private int RequiredCpu; - private long RequiredMemory; - private int WaitTime; - - public EventHandler OnUpdated; + #endregion - public CleanupService(ConfigService configService, IServiceScopeFactory serviceScopeFactory) + private readonly ConfigService ConfigService; + private readonly MessageService MessageService; + private readonly IServiceScopeFactory ServiceScopeFactory; + private readonly PeriodicTimer Timer; + + public CleanupService( + ConfigService configService, + IServiceScopeFactory serviceScopeFactory, + MessageService messageService) { ServiceScopeFactory = serviceScopeFactory; + MessageService = messageService; ConfigService = configService; - - var config = configService.GetSection("Moonlight").GetSection("Cleanup"); - - RequiredCpu = config.GetValue("Cpu"); - RequiredMemory = config.GetValue("Memory"); - WaitTime = config.GetValue("Wait"); - - if (!ConfigService.DebugMode) - Task.Run(Start); - } - - private void Start() - { + StartedAt = DateTime.Now; CompletedAt = DateTime.Now; IsRunning = false; - Activated = true; + + var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup"); - DoWaiting(); - } - - private async void DoWaiting() - { - while (true) + if (!config.GetValue("Enable") || ConfigService.DebugMode) { - if (Activated) - await Perform(); - - try - { - await Task.Delay((int) TimeSpan.FromMinutes(WaitTime).TotalMilliseconds); - } - catch (Exception ex) - { - } + Logger.Info("Disabling cleanup service"); + return; } - } - - public async Task TriggerPerform() - { - if (IsRunning) - return; + + Timer = new(TimeSpan.FromMinutes(config.GetValue("Wait"))); - PerformTask = new Task(async () => await Perform()); - PerformTask.Start(); + Task.Run(Run); } - private async Task Perform() + private async Task Run() { - if (IsRunning) - return; - - IsRunning = true; - StartedAt = DateTime.Now; - ServersRunning = 0; - - OnUpdated?.Invoke(this, null); - - using (var scope = ServiceScopeFactory.CreateScope()) + while (await Timer.WaitForNextTickAsync()) { - // Setup time measure - var watch = new Stopwatch(); - watch.Start(); + IsRunning = true; - // Get repos from dependency injection - var serverRepository = scope.ServiceProvider.GetRequiredService(); + using var scope = ServiceScopeFactory.CreateScope(); + var config = ConfigService.GetSection("Moonlight").GetSection("Cleanup"); + + var maxCpu = config.GetValue("Cpu"); + var minMemory = config.GetValue("Memory"); + var maxUptime = config.GetValue("Uptime"); + var minUptime = config.GetValue("MinUptime"); + var nodeRepository = scope.ServiceProvider.GetRequiredService(); var nodeService = scope.ServiceProvider.GetRequiredService(); - var imageRepo = scope.ServiceProvider.GetRequiredService(); - var serverService = scope.ServiceProvider.GetRequiredService(); - // Fetching data from mysql - var servers = serverRepository.Get() - .Include(x => x.Image) + var nodes = nodeRepository + .Get() .ToArray(); - var nodes = nodeRepository.Get().ToArray(); - var exceptions = servers.Where(x => x.IsCleanupException); - var images = imageRepo.Get().ToArray(); - var nodeCount = nodes.Count(); - - // We use this counter for the foreach loops - int counter = 0; - PercentProgress = 0; - - // Fetching data from nodes so we know what nodes to scan - var nodeContainers = new Dictionary(); - - Status = "Checking Nodes"; - counter = 0; - PercentProgress = 0; - OnUpdated?.Invoke(this, null); - foreach (var node in nodes) { try { - var cpu = await nodeService.GetCpuStats(node); - var freeMemory = await nodeService.GetMemoryStats(node); + var cpuStats = await nodeService.GetCpuStats(node); + var memoryStats = await nodeService.GetMemoryStats(node); - if (cpu.Usage > RequiredCpu || freeMemory.Free < RequiredMemory) + if (cpuStats.Usage > maxCpu || memoryStats.Free < minMemory) { - var c = await nodeService.GetContainerStats(node); - var containers = c.Containers; - nodeContainers.Add(node, containers.ToArray()); - } - } - catch (Exception e) - { - Logger.Error($"Error fetching cleanup data from node {node.Id}"); - Logger.Error(e); - } + var containerStats = await nodeService.GetContainerStats(node); - counter++; - CalculateAndUpdateProgress(counter, nodeCount); - OnUpdated?.Invoke(this, null); - } + var serverRepository = scope.ServiceProvider.GetRequiredService(); + var imageRepository = scope.ServiceProvider.GetRequiredService(); - // Searching for servers we can actually stop because they have the cleanup tag - // and determine which servers we have to check for an illegal mc server - var serversToCheck = new List(); - var serversToCheckForMc = new List(); - - Status = "Checking found servers"; - counter = 0; - PercentProgress = 0; - OnUpdated?.Invoke(this, null); - - // Count every container for progress calculation - var allContainers = 0; - foreach (var array in nodeContainers) - { - allContainers += array.Value.Length; - } - - foreach (var nodeContainer in nodeContainers) - { - try - { - foreach (var container in nodeContainer.Value) - { - var server = servers.First(x => x.Uuid.ToString() == container.Name); - var tagsJson = imageRepo + var images = imageRepository .Get() - .First(x => x.Id == server.Image.Id).TagsJson; + .ToArray(); - var tags = JsonConvert.DeserializeObject(tagsJson) ?? Array.Empty(); + var imagesWithFlag = images + .Where(x => + (JsonConvert.DeserializeObject(x.TagsJson) ?? Array.Empty()).Contains("cleanup") + ) + .ToArray(); + + var containerMappedToServers = new Dictionary(); - if (tags.FirstOrDefault(x => x == "cleanup") != null) + foreach (var container in containerStats.Containers) { - serversToCheck.Add(server); - } - - if (tags.FirstOrDefault(x => x == "illegalmc") != null) - { - serversToCheckForMc.Add(server); - } - } - } - catch (Exception e) - { - Logger.Error($"Error processing cleanup data from node {nodeContainer.Key.Id}"); - Logger.Error(e); - } - - counter++; - CalculateAndUpdateProgress(counter, allContainers); - OnUpdated?.Invoke(this, null); - } - - // Now we gonna scan every tagged server - Status = "Scanning servers"; - counter = 0; - PercentProgress = 0; - OnUpdated?.Invoke(this, null); - - foreach (var server in serversToCheck) - { - try - { - var serverData = serverRepository - .Get() - .Include(x => x.MainAllocation) - .Include(x => x.Node) - .Include(x => x.Variables) - .First(x => x.Id == server.Id); - - var players = GetPlayers(serverData.Node, serverData.MainAllocation); - var stats = await serverService.GetDetails(server); - - var exception = exceptions.FirstOrDefault(x => x.Id == server.Id) != null; - - if (stats != null) - { - if (exception) - { - if (players == 0 && stats.Utilization.Uptime > TimeSpan.FromHours(6).TotalMilliseconds) + if (Guid.TryParse(container.Name, out Guid uuid)) { - await serverService.SetPowerState(server, PowerSignal.Restart); - ServersCleaned++; - OnUpdated?.Invoke(this, null); - } - else - { - ServersRunning++; - OnUpdated?.Invoke(this, null); - } - } - else - { - if (players == 0 && stats.Utilization.Uptime > TimeSpan.FromMinutes(10).TotalMilliseconds) - { - var cleanupVar = serverData.Variables.FirstOrDefault(x => x.Key == "J2S"); + var server = serverRepository + .Get() + .Include(x => x.Image) + .Include(x => x.MainAllocation) + .Include(x => x.Variables) + .FirstOrDefault(x => x.Uuid == uuid); - if (cleanupVar == null) + if (server != null && imagesWithFlag.Any(y => y.Id == server.Image.Id)) { - await serverService.SetPowerState(server, PowerSignal.Stop); - ServersCleaned++; - OnUpdated?.Invoke(this, null); + containerMappedToServers.Add(container, server); + } + } + } + + var serverService = scope.ServiceProvider.GetRequiredService(); + + foreach (var containerMapped in containerMappedToServers) + { + var server = containerMapped.Value; + + try + { + var stats = await serverService.GetDetails(server); + + if (server.IsCleanupException) + { + if (stats.Utilization.Uptime > TimeSpan.FromHours(maxUptime).TotalMilliseconds) + { + var players = GetPlayers(node, server.MainAllocation); + + if (players == 0) + { + await serverService.SetPowerState(server, PowerSignal.Restart); + + ServersCleaned++; + } + else + { + ServersRunning++; + } + + await MessageService.Emit("cleanup.updated", null); + } } else { - if (cleanupVar.Value == "1") + if (stats.Utilization.Uptime > TimeSpan.FromMinutes(minUptime).TotalMilliseconds) { - await serverService.SetPowerState(server, PowerSignal.Restart); - ServersCleaned++; - OnUpdated?.Invoke(this, null); - } - else - { - await serverService.SetPowerState(server, PowerSignal.Stop); - ServersCleaned++; - OnUpdated?.Invoke(this, null); + var players = GetPlayers(node, server.MainAllocation); + + if (players < 1) + { + var j2SVar = server.Variables.FirstOrDefault(x => x.Key == "J2S"); + var handleJ2S = j2SVar != null && j2SVar.Value == "1"; + + if (handleJ2S) + { + await serverService.SetPowerState(server, PowerSignal.Restart); + } + else + { + await serverService.SetPowerState(server, PowerSignal.Stop); + } + + ServersCleaned++; + } + else + { + ServersRunning++; + } + + await MessageService.Emit("cleanup.updated", null); } } } - else + catch (Exception e) { - ServersRunning++; - OnUpdated?.Invoke(this, null); + Logger.Warn($"Error checking server {server.Name} ({server.Id})"); + Logger.Warn(e); } } } } catch (Exception e) { - Logger.Error($"Error scanning {server.Name}"); + Logger.Error($"Error performing cleanup on node {node.Name} ({node.Id})"); Logger.Error(e); } - - counter++; - CalculateAndUpdateProgress(counter, serversToCheck.Count); - OnUpdated?.Invoke(this, null); } - - // Finally we have to check all code container allocations - // for illegal hosted mc servers - - Status = "Scanning code containers"; - counter = 0; - PercentProgress = 0; - OnUpdated?.Invoke(this, null); - foreach (var server in serversToCheckForMc) - { - try - { - var serverData = serverRepository - .Get() - .Include(x => x.Allocations) - .Include(x => x.Node) - .First(x => x.Id == server.Id); - - foreach (var allocation in serverData.Allocations) - { - if (GetPlayers(server.Node, allocation) != -1) - { - // TODO: Suspend server - Logger.Warn("Found CC running mc: https://moonlight.endelon-hosting.de/server/" + - server.Uuid + "/"); - } - } - } - catch (Exception e) - { - Logger.Error($"Error scanning (cc) {server.Name}"); - Logger.Error(e); - } - - counter++; - CalculateAndUpdateProgress(counter, serversToCheckForMc.Count); - OnUpdated?.Invoke(this, null); - } - - watch.Stop(); - - Status = $"Cleanup finished. Duration: {Math.Round(TimeSpan.FromMilliseconds(watch.ElapsedMilliseconds).TotalMinutes, 2)} Minutes"; - PercentProgress = 100; - OnUpdated?.Invoke(this, null); + IsRunning = false; + CleanupsPerformed++; + await MessageService.Emit("cleanup.updated", null); } - - IsRunning = false; - CompletedAt = DateTime.Now; - CleanupsPerformed++; - OnUpdated?.Invoke(this, null); } private int GetPlayers(Node node, NodeAllocation allocation) { var ms = new MineStat(node.Fqdn, (ushort)allocation.Port); + + //TODO: Add fake player check if (ms.ServerUp) { return ms.CurrentPlayersInt; } - else - { - return -1; - } - } - - private void CalculateAndUpdateProgress(int now, int all) - { - PercentProgress = (int)Math.Round((now / (double)all) * 100); + + return -1; } } \ No newline at end of file diff --git a/Moonlight/App/Services/ConfigService.cs b/Moonlight/App/Services/ConfigService.cs index 7a315cb2..ed124f62 100644 --- a/Moonlight/App/Services/ConfigService.cs +++ b/Moonlight/App/Services/ConfigService.cs @@ -1,4 +1,5 @@ using System.Text; +using Logging.Net; using Microsoft.Extensions.Primitives; using Moonlight.App.Helpers; @@ -21,6 +22,9 @@ public class ConfigService : IConfiguration if (debugVar != null) DebugMode = bool.Parse(debugVar); + + if(DebugMode) + Logger.Debug("Debug mode enabled"); } public IEnumerable GetChildren() diff --git a/Moonlight/Shared/Views/Admin/Servers/Cleanup.razor b/Moonlight/Shared/Views/Admin/Servers/Cleanup.razor new file mode 100644 index 00000000..41766180 --- /dev/null +++ b/Moonlight/Shared/Views/Admin/Servers/Cleanup.razor @@ -0,0 +1,103 @@ +@page "/admin/servers/cleanup" + +@using Moonlight.App.Services +@using Moonlight.App.Models.Misc +@using Moonlight.App.Services.LogServices + +@inject CleanupService CleanupService +@inject AuditLogService AuditLogService +@inject MessageService MessageService + +@implements IDisposable + + +
+
+
+
+
+ +
+
+
+
+ @(CleanupService.ServersCleaned) +
+ + Servers + + + stopped + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ @(CleanupService.CleanupsPerformed) +
+ + Cleanups + + + executed + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ @(CleanupService.ServersRunning) +
+ + Running cleanup + + + servers + +
+
+
+
+
+
+
+ +@code +{ + protected override void OnInitialized() + { + MessageService.Subscribe("cleanup.updated", this, _ => + { + Task.Run(async () => + { + await InvokeAsync(StateHasChanged); + }); + + return Task.CompletedTask; + }); + } + + public void Dispose() + { + MessageService.Unsubscribe("cleanup.updated", this); + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Views/Admin/Servers/Cleanup/Index.razor b/Moonlight/Shared/Views/Admin/Servers/Cleanup/Index.razor deleted file mode 100644 index b87c04a3..00000000 --- a/Moonlight/Shared/Views/Admin/Servers/Cleanup/Index.razor +++ /dev/null @@ -1,200 +0,0 @@ -@page "/admin/servers/cleanup" - -@using Moonlight.App.Services -@using Logging.Net -@using Moonlight.App.Models.Misc -@using Moonlight.App.Services.LogServices - -@inject CleanupService CleanupService -@inject AuditLogService AuditLogService - -@implements IDisposable - - - Cleanup - - - -
-
-
-
-
- -
-
-
-
- @(CleanupService.ServersCleaned) -
- - Servers - - - stopped - -
-
-
-
-
-
-
-
-
- -
-
-
-
- @(CleanupService.CleanupsPerformed) -
- - Cleanups - - - executed - -
-
-
-
-
-
-
-
-
- -
-
-
-
- @(CleanupService.ServersRunning) -
- - Used clanup - - - Servers - -
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
- @if (CleanupService.IsRunning) - { - - } - else - { - - } -
-
- @if (CleanupService.Activated) - { - - } - else - { - - } -
-
- @if (CleanupService.Activated) - { - - } - else - { - - } -
-
-
-
-
-
-
-
- - - - -@code -{ - - protected async override Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - CleanupService.OnUpdated += OnUpdated; - } - } - - private void OnUpdated(object? sender, EventArgs e) - { - Logger.Debug(CleanupService.PercentProgress); - - InvokeAsync(StateHasChanged); - } - - public void Dispose() - { - CleanupService.OnUpdated -= OnUpdated; - } - - private void Enable() - { - CleanupService.Activated = true; - AuditLogService.Log(AuditLogType.CleanupEnabled, "The automatic cleanup has been activated"); - InvokeAsync(StateHasChanged); - } - - private void Disabled() - { - CleanupService.Activated = false; - AuditLogService.Log(AuditLogType.CleanupDisabled, "The automatic cleanup has been disabled"); - InvokeAsync(StateHasChanged); - } - - private async void Trigger() - { - await CleanupService.TriggerPerform(); - await AuditLogService.Log(AuditLogType.CleanupTriggered, "The automatic cleanup has been manually triggered"); - await InvokeAsync(StateHasChanged); - } -} \ No newline at end of file diff --git a/Moonlight/resources/lang/de_de.lang b/Moonlight/resources/lang/de_de.lang index aed88df3..c14f200b 100644 --- a/Moonlight/resources/lang/de_de.lang +++ b/Moonlight/resources/lang/de_de.lang @@ -422,3 +422,5 @@ Select javascript file to execute on start;Select javascript file to execute on Submit;Submit Processing;Processing Go up;Go up +Running cleanup;Running cleanup +servers;servers