526 lines
17 KiB
C#
526 lines
17 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
using System.Net;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
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 DockerServiceDto
|
|
{
|
|
public required int Server_id { get; set; } // Vom Watcher-Server zugewiesene ID des Hosts
|
|
public required JsonElement Containers { get; set; }
|
|
}
|
|
|
|
public class DockerServiceMetricDto
|
|
{
|
|
|
|
}
|
|
|
|
[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] DockerServiceDto 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 });
|
|
}
|
|
|
|
// Prüfen, ob der Server existiert
|
|
var serverExists = await _context.Servers.AnyAsync(s => s.Id == dto.Server_id);
|
|
if (!serverExists)
|
|
{
|
|
_logger.LogError($"Server with ID {dto.Server_id} does not exist.");
|
|
return BadRequest(new { error = "Server not found", details = $"Server with ID {dto.Server_id} does not exist. Please register the server first." });
|
|
}
|
|
|
|
List<Container> newContainers =
|
|
JsonSerializer.Deserialize<List<Container>>(dto.Containers.GetRawText())
|
|
?? new List<Container>();
|
|
|
|
foreach (Container container in newContainers)
|
|
{
|
|
container.ServerId = dto.Server_id;
|
|
// Debug Logs
|
|
// TODO entfernen wenn fertig getestet
|
|
Console.WriteLine("---------");
|
|
Console.WriteLine("ServerId: " + container.ServerId);
|
|
Console.WriteLine("ContainerId: " + container.ContainerId);
|
|
Console.WriteLine("Name: " + container.Name);
|
|
Console.WriteLine("Image: " + container.Image);
|
|
Console.WriteLine("---------");
|
|
|
|
}
|
|
|
|
// 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 container in newContainers)
|
|
{
|
|
// Überprüfen, ob ein übergebener Container bereits für den Host registriert ist
|
|
// Wichtig: Vergleich über ContainerId, nicht über Objektreferenz!
|
|
var existingContainer = existingContainers
|
|
.FirstOrDefault(ec => ec.ContainerId == container.ContainerId);
|
|
|
|
if (existingContainer != null)
|
|
{
|
|
// Container existiert bereits, nur Daten aktualisieren falls sich etwas geändert hat
|
|
existingContainer.Name = container.Name;
|
|
existingContainer.Image = container.Image;
|
|
existingContainer.IsRunning = true;
|
|
|
|
_logger.LogInformation("Container '{containerName}' (ID: {containerId}) already exists for Server {serverId}, updated.", container.Name, container.ContainerId, dto.Server_id);
|
|
}
|
|
// Container auf einen Host/Server registrieren
|
|
else
|
|
{
|
|
// Container in Datenbank einlesen
|
|
try
|
|
{
|
|
_context.Containers.Add(container);
|
|
await _context.SaveChangesAsync();
|
|
_logger.LogInformation("Container '{containerName}' (ID: {containerId}) added for Server {serverId}", container.Name, container.ContainerId, container.ServerId);
|
|
}
|
|
catch (SqliteException e)
|
|
{
|
|
_logger.LogError("Error writing new Container '{containerName}' to Database: {error}", container.Name, e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Logik um abgeschaltene Container aus der Datenbank zu entfernen
|
|
foreach (Container existingContainer in existingContainers)
|
|
{
|
|
// Abfrage, ob bereits vorhandener Container im Payload vorhanden war
|
|
// Wichtig: Vergleich über ContainerId, nicht über Objektreferenz!
|
|
var stillRunning = newContainers
|
|
.Any(nc => nc.ContainerId == existingContainer.ContainerId);
|
|
|
|
if (!stillRunning)
|
|
{
|
|
// Container entfernen
|
|
_context.Containers.Remove(existingContainer);
|
|
await _context.SaveChangesAsync();
|
|
|
|
// Metrics für den Container entfernen
|
|
//Todo
|
|
|
|
_logger.LogInformation("Container '{containerName}' (DB-ID: {id}, ContainerID: {containerId}) on Server {serverId} was removed from the database.", existingContainer.Name, existingContainer.Id, existingContainer.ContainerId, existingContainer.ServerId);
|
|
}
|
|
}
|
|
|
|
// Alle Änderungen in einem Batch speichern
|
|
await _context.SaveChangesAsync();
|
|
|
|
return Ok();
|
|
|
|
}
|
|
|
|
// Endpoint, an den der Agent die Metrics der registrierten Container schickt
|
|
public async Task<IActionResult> ServiceMetrics([FromBody] DockerServiceMetricDto 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 });
|
|
}
|
|
|
|
// Liste an Metrics aus der dto erstellen
|
|
List<ContainerMetric> metrics = new List<ContainerMetric>();
|
|
|
|
// Metrics in die Datenbank eintragen
|
|
try
|
|
{
|
|
foreach (ContainerMetric m in metrics)
|
|
{
|
|
_context.ContainerMetrics.Add(m);
|
|
await _context.SaveChangesAsync();
|
|
// _logger.LogInformation(m. + " added for Host " + c.ServerId);
|
|
return Ok();
|
|
}
|
|
}
|
|
catch (SqliteException e)
|
|
{
|
|
_logger.LogError(e.Message);
|
|
return StatusCode(500);
|
|
}
|
|
|
|
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, int hours = 1)
|
|
{
|
|
var startTime = DateTime.UtcNow.AddHours(-hours);
|
|
var metrics = await _context.Metrics
|
|
.Where(m => m.Timestamp >= startTime && m.ServerId == serverId)
|
|
.OrderBy(m => m.Timestamp)
|
|
.ToListAsync();
|
|
|
|
// Timestamp-Format basierend auf Zeitbereich anpassen
|
|
string format = hours > 1 ? "dd.MM HH:mm" : "HH:mm";
|
|
var data = metrics.Select(m => new
|
|
{
|
|
label = m.Timestamp.ToLocalTime().ToString(format),
|
|
data = m.CPU_Load
|
|
}).ToList();
|
|
|
|
return Ok(data);
|
|
}
|
|
|
|
[HttpGet("ram-usage")]
|
|
public async Task<IActionResult> GetRamUsageData(int serverId, int hours = 1)
|
|
{
|
|
var startTime = DateTime.UtcNow.AddHours(-hours);
|
|
var metrics = await _context.Metrics
|
|
.Where(m => m.Timestamp >= startTime && m.ServerId == serverId)
|
|
.OrderBy(m => m.Timestamp)
|
|
.ToListAsync();
|
|
|
|
// Timestamp-Format basierend auf Zeitbereich anpassen
|
|
string format = hours > 1 ? "dd.MM HH:mm" : "HH:mm";
|
|
var data = metrics.Select(m => new
|
|
{
|
|
label = m.Timestamp.ToLocalTime().ToString(format),
|
|
data = m.RAM_Load
|
|
}).ToList();
|
|
|
|
return Ok(data);
|
|
}
|
|
|
|
[HttpGet("gpu-usage")]
|
|
public async Task<IActionResult> GetGpuUsageData(int serverId, int hours = 1)
|
|
{
|
|
var startTime = DateTime.UtcNow.AddHours(-hours);
|
|
var metrics = await _context.Metrics
|
|
.Where(m => m.Timestamp >= startTime && m.ServerId == serverId)
|
|
.OrderBy(m => m.Timestamp)
|
|
.ToListAsync();
|
|
|
|
// Timestamp-Format basierend auf Zeitbereich anpassen
|
|
string format = hours > 1 ? "dd.MM HH:mm" : "HH:mm";
|
|
var data = metrics.Select(m => new
|
|
{
|
|
label = m.Timestamp.ToLocalTime().ToString(format),
|
|
data = m.GPU_Load
|
|
}).ToList();
|
|
|
|
return Ok(data);
|
|
}
|
|
|
|
[HttpGet("current-metrics/{serverId}")]
|
|
public async Task<IActionResult> GetCurrentMetrics(int serverId)
|
|
{
|
|
var latestMetric = await _context.Metrics
|
|
.Where(m => m.ServerId == serverId)
|
|
.OrderByDescending(m => m.Timestamp)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (latestMetric == null)
|
|
{
|
|
return Ok(new
|
|
{
|
|
cpu = 0.0,
|
|
ram = 0.0,
|
|
gpu = 0.0,
|
|
hasData = false
|
|
});
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
cpu = latestMetric.CPU_Load,
|
|
ram = latestMetric.RAM_Load,
|
|
gpu = latestMetric.GPU_Load,
|
|
hasData = true
|
|
});
|
|
}
|
|
|
|
// Metric Input Byte zu Gigabyte umwandeln
|
|
public static double CalculateGigabyte(double metricInput)
|
|
{
|
|
// *10^-9 um auf Gigabyte zu kommen
|
|
double calculatedValue = metricInput * Math.Pow(10, -9);
|
|
|
|
// Auf 2 Nachkommastellen runden
|
|
double calculatedValueSanitized = SanitizeInput(calculatedValue);
|
|
|
|
return calculatedValueSanitized;
|
|
}
|
|
|
|
// Metric Input Byte/s zu Megabit/s umrechnen
|
|
//TODO
|
|
public static double CalculateMegabit(double metricInput)
|
|
{
|
|
// *10^-9 um auf Gigabyte zu kommen
|
|
double calculatedValue = metricInput * Math.Pow(10, -9);
|
|
|
|
// Auf 2 Nachkommastellen runden
|
|
double calculatedValueSanitized = SanitizeInput(calculatedValue);
|
|
|
|
return calculatedValueSanitized;
|
|
}
|
|
|
|
// Degree Input auf zwei Nachkommastellen runden
|
|
public static double SanitizeInput(double metricInput)
|
|
{
|
|
Math.Round(metricInput, 2);
|
|
|
|
return metricInput;
|
|
}
|
|
|
|
private List<Container> ParseServiceDiscoveryInput(int serverId, List<Container> containers)
|
|
{
|
|
List<Container> containerList = new List<Container>();
|
|
|
|
// JSON-Objekt auslesen und Container-Objekte erstellen
|
|
|
|
|
|
return containerList;
|
|
}
|
|
} |