151 lines
5.4 KiB
C#
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] + "…";
|
|
}
|
|
}
|