Files
123123/ZA.CoreService.ESBCertificateManager/Services/SonicBinRestartExecutor.cs
T

246 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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] + "…";
}