246 lines
9.0 KiB
C#
246 lines
9.0 KiB
C#
using System.Diagnostics;
|
||
using System.Text;
|
||
using ZA.CoreService.ESBCertificateManager.Models;
|
||
|
||
namespace ZA.CoreService.ESBCertificateManager.Services;
|
||
|
||
/// <summary>
|
||
/// Neustart über Sonic-Server-Scripts:
|
||
/// stopcontainer.bat / startcontainer.bat
|
||
/// Diese liegen nur in einer vollen MQ/ESB-Server-Installation – nicht in einer
|
||
/// reinen Sonic Management Console (SMC/Client).
|
||
/// </summary>
|
||
public sealed class SonicBinRestartExecutor
|
||
{
|
||
private readonly SonicConnection _connection;
|
||
|
||
public SonicBinRestartExecutor(SonicConnection connection)
|
||
{
|
||
_connection = connection;
|
||
}
|
||
|
||
public (bool Ok, string? Error, string? Detail) Probe()
|
||
{
|
||
string sonicHome = _connection.SonicHome?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(sonicHome))
|
||
{
|
||
return (false, "SonicHome ist leer – in appsettings setzen.", null);
|
||
}
|
||
|
||
if (!Directory.Exists(sonicHome))
|
||
{
|
||
return (false, $"SonicHome existiert nicht: '{sonicHome}'", null);
|
||
}
|
||
|
||
(string? stop, string? start, string searchNote) = LocateContainerScripts(sonicHome);
|
||
if (stop is null || start is null)
|
||
{
|
||
string binDir = Path.Combine(sonicHome, "bin");
|
||
string binListing = DescribeDirectory(binDir);
|
||
bool looksLikeSmcOnly = Directory.Exists(Path.Combine(sonicHome, "lib"))
|
||
&& !File.Exists(Path.Combine(binDir, "stopcontainer.bat"));
|
||
|
||
string why = looksLikeSmcOnly
|
||
? "Das sieht nach einer Sonic Management Console / Client-Installation aus "
|
||
+ "(lib vorhanden, aber keine Server-Scripts). "
|
||
+ "stopcontainer.bat gibt es nur auf dem Sonic-SERVER, nicht in der reinen SMC."
|
||
: "Server-Scripts wurden unter SonicHome nicht gefunden.";
|
||
|
||
return (false,
|
||
why + $" Gesucht unter '{sonicHome}'.",
|
||
$"{searchNote}\nInhalt von bin: {binListing}\n"
|
||
+ "Lösung A: SonicHome auf den Server-Installationspfad setzen (dort wo stopcontainer.bat liegt).\n"
|
||
+ "Lösung B: App lässt automatisch MfApi/SMC-Verbindung versuchen (ConnectionUrl + Login).");
|
||
}
|
||
|
||
return (true, null, $"stop={stop}; start={start}");
|
||
}
|
||
|
||
public async Task<(bool Success, string Status, string? Detail)> RestartAsync(
|
||
string containerName,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
(bool probeOk, string? probeError, string? probeDetail) = Probe();
|
||
if (!probeOk)
|
||
{
|
||
return (false, "Neustart fehlgeschlagen (SonicBin)",
|
||
$"{probeError}\n{probeDetail}");
|
||
}
|
||
|
||
string sonicHome = _connection.SonicHome.Trim();
|
||
(string? stopBat, string? startBat, _) = LocateContainerScripts(sonicHome);
|
||
string bin = Path.GetDirectoryName(stopBat!)!;
|
||
|
||
string shortName = containerName.Contains('.')
|
||
? containerName[(containerName.IndexOf('.') + 1)..]
|
||
: containerName;
|
||
string fullName = containerName.Contains('.')
|
||
? containerName
|
||
: $"{_connection.DomainName}.{containerName}";
|
||
|
||
StringBuilder log = new();
|
||
log.AppendLine($"SonicHome={sonicHome}");
|
||
log.AppendLine($"stop={stopBat}");
|
||
log.AppendLine($"start={startBat}");
|
||
log.AppendLine($"Container={fullName} (kurz={shortName})");
|
||
|
||
(bool stopOk, string stopOut, string stopErr, int stopCode) =
|
||
await RunBatAsync(stopBat!, fullName, bin, cancellationToken);
|
||
log.AppendLine($"STOP ExitCode={stopCode}");
|
||
if (stopOut.Length > 0) log.AppendLine("STOP out: " + Truncate(stopOut));
|
||
if (stopErr.Length > 0) log.AppendLine("STOP err: " + Truncate(stopErr));
|
||
|
||
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
|
||
|
||
(bool startOk, string startOut, string startErr, int startCode) =
|
||
await RunBatAsync(startBat!, fullName, bin, cancellationToken);
|
||
log.AppendLine($"START ExitCode={startCode}");
|
||
if (startOut.Length > 0) log.AppendLine("START out: " + Truncate(startOut));
|
||
if (startErr.Length > 0) log.AppendLine("START err: " + Truncate(startErr));
|
||
|
||
if (startCode != 0
|
||
&& !string.Equals(shortName, fullName, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
log.AppendLine($"Retry START mit Kurzname '{shortName}'…");
|
||
(_, startOut, startErr, startCode) =
|
||
await RunBatAsync(startBat!, shortName, bin, cancellationToken);
|
||
log.AppendLine($"START(short) ExitCode={startCode}");
|
||
if (startOut.Length > 0) log.AppendLine("START out: " + Truncate(startOut));
|
||
if (startErr.Length > 0) log.AppendLine("START err: " + Truncate(startErr));
|
||
}
|
||
|
||
await Task.Delay(
|
||
TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)),
|
||
cancellationToken);
|
||
|
||
if (startCode != 0)
|
||
{
|
||
return (false, "Neustart fehlgeschlagen (SonicBin)",
|
||
$"startcontainer ExitCode={startCode} (stop ExitCode={stopCode}).\n{log}");
|
||
}
|
||
|
||
return (true, $"Container '{fullName}' neugestartet (SonicBin)", log.ToString());
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sucht stop/startcontainer.bat unter SonicHome\bin und rekursiv (max. Tiefe 4).
|
||
/// </summary>
|
||
public static (string? StopBat, string? StartBat, string Note) LocateContainerScripts(string sonicHome)
|
||
{
|
||
List<string> candidates = [];
|
||
|
||
void AddDir(string? dir)
|
||
{
|
||
if (!string.IsNullOrWhiteSpace(dir) && Directory.Exists(dir) && !candidates.Contains(dir))
|
||
{
|
||
candidates.Add(dir);
|
||
}
|
||
}
|
||
|
||
AddDir(Path.Combine(sonicHome, "bin"));
|
||
AddDir(Path.Combine(sonicHome, "MQ_HOME", "bin"));
|
||
AddDir(Path.Combine(sonicHome, "MQ", "bin"));
|
||
|
||
try
|
||
{
|
||
string? parent = Directory.GetParent(sonicHome)?.FullName;
|
||
if (parent is not null)
|
||
{
|
||
foreach (string child in Directory.EnumerateDirectories(parent))
|
||
{
|
||
AddDir(Path.Combine(child, "bin"));
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// ignore
|
||
}
|
||
|
||
// Rekursiv nach Dateinamen suchen
|
||
try
|
||
{
|
||
foreach (string file in Directory.EnumerateFiles(sonicHome, "stopcontainer.bat", SearchOption.AllDirectories)
|
||
.Take(20))
|
||
{
|
||
AddDir(Path.GetDirectoryName(file));
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// ignore permission issues
|
||
}
|
||
|
||
foreach (string dir in candidates)
|
||
{
|
||
string stop = Path.Combine(dir, "stopcontainer.bat");
|
||
string start = Path.Combine(dir, "startcontainer.bat");
|
||
if (File.Exists(stop) && File.Exists(start))
|
||
{
|
||
return (stop, start, $"Scripts gefunden in '{dir}'");
|
||
}
|
||
}
|
||
|
||
return (null, null, $"Keine Scripts in {candidates.Count} geprüften bin-Ordnern.");
|
||
}
|
||
|
||
private static string DescribeDirectory(string dir)
|
||
{
|
||
if (!Directory.Exists(dir))
|
||
{
|
||
return "(Ordner existiert nicht)";
|
||
}
|
||
|
||
try
|
||
{
|
||
string[] names = Directory.GetFileSystemEntries(dir)
|
||
.Select(Path.GetFileName)
|
||
.Where(n => n is not null)
|
||
.Cast<string>()
|
||
.OrderBy(n => n)
|
||
.Take(25)
|
||
.ToArray();
|
||
return names.Length == 0 ? "(leer)" : string.Join(", ", names);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return $"(nicht lesbar: {ex.Message})";
|
||
}
|
||
}
|
||
|
||
private static async Task<(bool Started, string StdOut, string StdErr, int ExitCode)> RunBatAsync(
|
||
string batPath,
|
||
string argument,
|
||
string workingDirectory,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
ProcessStartInfo psi = new()
|
||
{
|
||
FileName = "cmd.exe",
|
||
Arguments = $"/c \"\"{batPath}\" \"{argument}\"\"",
|
||
WorkingDirectory = workingDirectory,
|
||
UseShellExecute = false,
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true,
|
||
CreateNoWindow = true,
|
||
StandardOutputEncoding = Encoding.UTF8,
|
||
StandardErrorEncoding = Encoding.UTF8
|
||
};
|
||
|
||
using Process process = new() { StartInfo = psi };
|
||
if (!process.Start())
|
||
{
|
||
return (false, string.Empty, "cmd.exe konnte nicht gestartet werden.", -1);
|
||
}
|
||
|
||
Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
|
||
Task<string> stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
|
||
|
||
await process.WaitForExitAsync(cancellationToken);
|
||
return (true, (await stdoutTask).Trim(), (await stderrTask).Trim(), process.ExitCode);
|
||
}
|
||
|
||
private static string Truncate(string value, int max = 500)
|
||
=> value.Length <= max ? value : value[..max] + "…";
|
||
}
|