Files
watcher/Watcher/Controllers/MonitoringController.cs
2025-10-29 08:50:16 +01:00

422 lines
14 KiB
C#

using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.CodeAnalysis;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Watcher.Data;
using Watcher.Models;
using Watcher.ViewModels;
namespace Watcher.Controllers;
public class HardwareDto
{
// Server Identity
[Required]
public int Id { get; set; }
[Required]
public string? IpAddress { get; set; }
// Hardware Info
[Required]
public string? CpuType { get; set; }
[Required]
public int CpuCores { get; set; }
[Required]
public string? GpuType { get; set; }
[Required]
public double RamSize { get; set; }
}
public class MetricDto
{
// Server Identity
[Required]
public int ServerId { get; set; }
[Required]
public string? IpAddress { get; set; }
// Hardware Metrics
// CPU
public double CPU_Load { get; set; } // %
public double CPU_Temp { get; set; } // deg C
// GPU
public double GPU_Load { get; set; } // %
public double GPU_Temp { get; set; } // deg C
public double GPU_Vram_Size { get; set; } // Bytes
public double GPU_Vram_Load { get; set; } // %
// RAM
public double RAM_Size { get; set; } // Bytes
public double RAM_Load { get; set; } // %
// Disks
public double DISK_Size { get; set; } // Bytes
public double DISK_Usage { get; set; } // Bytes
public double DISK_Temp { get; set; } // deg C (if available)
// Network
public double NET_In { get; set; } // Bytes/s
public double NET_Out { get; set; } // Bytes/s
}
public class ServiceDto
{
public required int server_id { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts
public required JsonContent containers { get; set; }
}
[ApiController]
[Route("[controller]")]
public class MonitoringController : Controller
{
private readonly AppDbContext _context;
private readonly ILogger<MonitoringController> _logger;
public MonitoringController(AppDbContext context, ILogger<MonitoringController> logger)
{
_context = context;
_logger = logger;
}
// Endpoint, an den der Agent seine Hardwareinformationen schickt
[HttpPost("hardware-info")]
public async Task<IActionResult> Register([FromBody] HardwareDto dto)
{
// Gültigkeit des Payloads prüfen
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
_logger.LogError("Fehlerhafter Registrierungs-Payload.");
return BadRequest(new { error = "Ungültiger Payload", details = errors });
}
// Server in Datenbank finden
var server = await _context.Servers
.FirstOrDefaultAsync(s => s.IPAddress == dto.IpAddress);
if (server != null)
{
// Serverdaten in Datenbank eintragen
server.CpuType = dto.CpuType;
server.CpuCores = dto.CpuCores;
server.GpuType = dto.GpuType;
server.RamSize = dto.RamSize;
// Änderungen in Datenbank speichern
await _context.SaveChangesAsync();
// Success
_logger.LogInformation("Agent für '{server}' erfolgreich registriert.", server.Name);
return Ok();
}
_logger.LogError("Kein Server für Registrierung gefunden");
return NotFound("No Matching Server found.");
}
// Endpoint, an dem sich ein Agent initial registriert
[HttpGet("register")]
public async Task<IActionResult> GetServerIdByIp([FromQuery] string IpAddress)
{
var server = await _context.Servers
.FirstOrDefaultAsync(s => s.IPAddress == IpAddress);
if (server == null)
return NotFound();
return Ok(new
{
id = server.Id,
IpAddress = server.IPAddress
});
}
// Enpoint, an den Agents Ihre gesammelten Daten senden
[HttpPost("metric")]
public async Task<IActionResult> Receive([FromBody] MetricDto dto)
{
// Gültigkeit des Payloads prüfen
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
_logger.LogError("Ungültiger Monitoring-Payload.");
return BadRequest(new { error = "Ungültiger Payload", details = errors });
}
// Server in Datenbank finden
var server = await _context.Servers
.FirstOrDefaultAsync(s => s.IPAddress == dto.IpAddress);
if (server != null)
{
// neues Metric-Objekt erstellen
var NewMetric = new Metric
{
Timestamp = DateTime.UtcNow,
ServerId = dto.ServerId,
CPU_Load = sanitizeInput(dto.CPU_Load),
CPU_Temp = sanitizeInput(dto.CPU_Temp),
GPU_Load = sanitizeInput(dto.GPU_Load),
GPU_Temp = sanitizeInput(dto.GPU_Temp),
GPU_Vram_Size = calculateGigabyte(dto.GPU_Vram_Size),
GPU_Vram_Usage = sanitizeInput(dto.GPU_Vram_Load),
RAM_Load = sanitizeInput(dto.RAM_Load),
RAM_Size = calculateGigabyte(dto.RAM_Size),
DISK_Size = calculateGigabyte(dto.DISK_Size),
DISK_Usage = calculateGigabyte(dto.DISK_Usage),
DISK_Temp = sanitizeInput(dto.DISK_Temp),
NET_In = calculateMegabit(dto.NET_In),
NET_Out = calculateMegabit(dto.NET_Out)
};
try
{
// Metric Objekt in Datenbank einfügen
_context.Metrics.Add(NewMetric);
await _context.SaveChangesAsync();
_logger.LogInformation("Monitoring-Daten für '{server}' empfangen", server.Name);
return Ok();
}
catch
{
// Alert triggern
_logger.LogError("Metric für {server} konnte nicht in Datenbank geschrieben werden.", server.Name);
return BadRequest();
}
}
_logger.LogError("Kein Server für eingegangenen Monitoring-Payload gefunden");
return NotFound("No Matching Server found.");
}
// Endpoint, an dem Agents Ihre laufenden Services registrieren
[HttpPost("service-discovery")]
public async Task<IActionResult> ServiceDetection([FromBody] ServiceDto dto)
{
// Gültigkeit des Payloads prüfen
if (!ModelState.IsValid)
{
var errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList();
_logger.LogError("Invalid ServiceDetection-Payload.");
return BadRequest(new { error = "Invalid Payload", details = errors });
}
// Json zu was brauchbarem machen
string containersJson = await dto.containers.ReadAsStringAsync();
List<Container> newContainers = JsonSerializer.Deserialize<List<Container>>(containersJson)?? new List<Container>();;
foreach (Container c in newContainers)
{
c.ServerId = dto.server_id;
// Debug Logs
// TODO entfernen wenn fertig getestet
Console.WriteLine("---------");
Console.WriteLine("ServerId: " + c.ServerId);
Console.WriteLine("ContainerId: " + c.ContainerId);
Console.WriteLine("Name: " + c.Name);
Console.WriteLine("Image: " + c.Image);
Console.WriteLine("---------");
}
// Container Objekte erstellen
//List<Container> newContainers = ParseServiceDiscoveryInput(dto.Server_id, containers);
// Liste aller Container, die bereits der übergebenen ServerId zugewiesen sind
List<Container> existingContainers = _context.Containers
.Where(c => c.ServerId == dto.server_id)
.ToList();
// Logik, um Container, die mit dem Payload kamen zu verarbeiten
foreach (Container c in newContainers)
{
// Überprüfen, ob ein übergebener Container bereits für den Host registriert ist
if (existingContainers.Contains(c))
{
_logger.LogInformation("Container with id " + c.ContainerId + " already exists.");
}
// Container auf einen Host/Server registrieren
else
{
// Container in Datenbank einlesen
try
{
_context.Containers.Add(c);
await _context.SaveChangesAsync();
_logger.LogInformation(c.Name + " added for Host " + c.ServerId);
}
catch (SqliteException e)
{
_logger.LogError("Error writing new Containers to Database: " + e.Message);
}
}
}
// Logik um abgeschaltene Container aus der Datenbank zu entfernen
foreach (Container c in existingContainers)
{
// Abfrage, ob bereits vorhandener Container im Payload vorhanden war
if (!newContainers.Contains(c))
{
// Container entfernen
_context.Containers.Remove(c);
await _context.SaveChangesAsync();
// Metrics für den Container entfernen
//Todo
_logger.LogInformation("Container " + c.Name + " (" + c.Id + ") on Host-Id " + c.ServerId + " was successfully removed from the database.");
}
}
return Ok();
}
// Durchschnittliche Werte Berechnen
public async Task<IActionResult> CalculateMedian(string Metric, int HoursToMonitor, int ServerId)
{
// Aktuelle Zeit - X Stunden = letzter Wert, der berücksichtigt werden soll
DateTime TimeToMonitor = DateTime.Now.AddHours(-HoursToMonitor);
// Alle Metrics von Server "ServerId" finden, die in der festgelegten Zeitspanne liegen
var MetricsInTimeFrame = await _context.Metrics
.Where(e => e.Timestamp >= TimeToMonitor && e.ServerId == ServerId)
.ToListAsync();
// return Action (MetricsInTimeFrame)
return NotFound();
}
[HttpGet("cpu-usage")]
public async Task<IActionResult> GetCpuUsageData(int serverId)
{
var oneDayAgo = DateTime.UtcNow.AddDays(-1);
var data = await _context.Metrics
.Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId)
.OrderBy(m => m.Timestamp)
.Select(m => new
{
label = m.Timestamp.ToUniversalTime().ToString("o"),
data = m.CPU_Load
})
.ToListAsync();
return Ok(data);
}
[HttpGet("ram-usage")]
public async Task<IActionResult> GetRamUsageData(int serverId)
{
var oneDayAgo = DateTime.UtcNow.AddDays(-1);
var data = await _context.Metrics
.Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId)
.OrderBy(m => m.Timestamp)
.Select(m => new
{
label = m.Timestamp.ToUniversalTime().ToString("o"),
data = m.RAM_Load
})
.ToListAsync();
return Ok(data);
}
[HttpGet("gpu-usage")]
public async Task<IActionResult> GetGpuUsageData(int serverId)
{
var oneDayAgo = DateTime.UtcNow.AddDays(-1);
var data = await _context.Metrics
.Where(m => m.Timestamp >= oneDayAgo && m.ServerId == serverId)
.OrderBy(m => m.Timestamp)
.Select(m => new
{
label = m.Timestamp.ToUniversalTime().ToString("o"),
data = m.GPU_Load
})
.ToListAsync();
return Ok(data);
}
// Metric Input Byte zu Gigabyte umwandeln
public static double calculateGigabyte(double metric_input)
{
// *10^-9 um auf Gigabyte zu kommen
double calculatedValue = metric_input * Math.Pow(10, -9);
// Auf 2 Nachkommastellen runden
double calculatedValue_s = sanitizeInput(calculatedValue);
return calculatedValue_s;
}
// Metric Input Byte/s zu Megabit/s umrechnen
//TODO
public static double calculateMegabit(double metric_input)
{
// *10^-9 um auf Gigabyte zu kommen
double calculatedValue = metric_input * Math.Pow(10, -9);
// Auf 2 Nachkommastellen runden
double calculatedValue_s = sanitizeInput(calculatedValue);
return calculatedValue_s;
}
// Degree Input auf zwei Nachkommastellen runden
public static double sanitizeInput(double metric_input)
{
Math.Round(metric_input, 2);
return metric_input;
}
private List<Container> ParseServiceDiscoveryInput(int server_id, List<Container> containers)
{
List<Container> containerList = new List<Container>();
// JSON-Objekt auslesen und Container-Objekte erstellen
return containerList;
}
}