Files
2026-07-24 08:10:13 +02:00

151 lines
5.4 KiB
C#

using System.Diagnostics;
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Services;
public sealed class RestartExecutor
{
private readonly IReadOnlyList<SonicConnection> _sonicConnections;
public RestartExecutor(IReadOnlyList<SonicConnection> sonicConnections)
{
_sonicConnections = sonicConnections;
}
public Task<(bool Success, string Status, string? Detail)> ExecuteAsync(
DeploymentTarget target,
CancellationToken cancellationToken = default)
=> target.RestartType switch
{
RestartType.None => Task.FromResult<(bool, string, string?)>((true, "Neustart übersprungen", "RestartType=None")),
RestartType.Command => ExecuteCommandAsync(target, cancellationToken),
RestartType.SonicContainer => ExecuteSonicRestartAsync(target, importXapi: false, cancellationToken),
RestartType.SonicContainerWithXapi => ExecuteSonicRestartAsync(target, importXapi: true, cancellationToken),
_ => Task.FromResult<(bool, string, string?)>((false, "Unbekannter RestartType", $"RestartType={target.RestartType}"))
};
private async Task<(bool Success, string Status, string? Detail)> ExecuteSonicRestartAsync(
DeploymentTarget target,
bool importXapi,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(target.ContainerName))
{
return (false, "Sonic-Neustart fehlgeschlagen", $"Ziel '{target.Name}': ContainerName fehlt.");
}
SonicConnection? connection = _sonicConnections
.FirstOrDefault(c => string.Equals(c.Name, target.SonicConnectionName, StringComparison.OrdinalIgnoreCase));
if (connection is null)
{
return (false, "Sonic-Verbindung nicht gefunden",
$"Ziel '{target.Name}': SonicConnection '{target.SonicConnectionName}' ist nicht in AppSettings konfiguriert.");
}
using SonicManagementClient client = new(connection);
if (importXapi)
{
if (string.IsNullOrWhiteSpace(target.XapiSourcePath))
{
return (false, "XApi-Import fehlgeschlagen",
$"Ziel '{target.Name}': XapiSourcePath fehlt bei RestartType=SonicContainerWithXapi.");
}
return await client.ImportXapiAndRestartAsync(target.ContainerName, target.XapiSourcePath, cancellationToken);
}
return await client.RestartContainerAsync(target.ContainerName, cancellationToken);
}
private async Task<(bool Success, string Status, string? Detail)> ExecuteCommandAsync(
DeploymentTarget target,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(target.RestartCommand))
{
return (true, "Neustart übersprungen", "RestartType=None");
}
int timeoutSeconds = Math.Clamp(target.RestartTimeoutSeconds, 1, 600);
ProcessStartInfo startInfo = new()
{
FileName = target.RestartCommand,
Arguments = target.RestartArguments ?? string.Empty,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
try
{
using Process process = new() { StartInfo = startInfo };
if (!process.Start())
{
return (false, "Neustart fehlgeschlagen", "Prozess konnte nicht gestartet werden.");
}
Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
Task<string> stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
try
{
await process.WaitForExitAsync(timeoutCts.Token);
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
try { process.Kill(entireProcessTree: true); } catch { /* ignore */ }
return (false, "Neustart Timeout", $"Timeout nach {timeoutSeconds}s.");
}
string detail = BuildDetail(process.ExitCode, await stdoutTask, await stderrTask);
return process.ExitCode == 0
? (true, "Neustart ok", detail)
: (false, "Neustart fehlgeschlagen", detail);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
return (false, "Neustart fehlgeschlagen", ex.Message);
}
}
private static string BuildDetail(int exitCode, string stdout, string stderr)
{
string detail = $"ExitCode={exitCode}";
stdout = Truncate(stdout).Trim();
if (!string.IsNullOrWhiteSpace(stdout))
{
detail += "; out=" + stdout;
}
stderr = Truncate(stderr).Trim();
if (!string.IsNullOrWhiteSpace(stderr))
{
detail += "; err=" + stderr;
}
return detail;
}
private static string Truncate(string value, int max = 400)
{
if (string.IsNullOrEmpty(value) || value.Length <= max)
{
return value;
}
return value[..max] + "…";
}
}