Fix restart without system Java via Sonic stop/startcontainer.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1827,17 +1827,18 @@ namespace ZA.CoreService.ESBCertificateManager
|
||||
.FirstOrDefault(c => string.Equals(c.Name, selectedName, StringComparison.OrdinalIgnoreCase))
|
||||
?? _sonicDiscovery.Connections[0];
|
||||
|
||||
(string sonicHome, string? javaExe) = new SonicMfApiExecutor(conn).ResolveRuntimePaths();
|
||||
string javaDisplay = string.IsNullOrWhiteSpace(javaExe) ? "java=?" : javaExe;
|
||||
string sonicHome = string.IsNullOrWhiteSpace(conn.SonicHome) ? "(leer)" : conn.SonicHome.Trim();
|
||||
(bool binOk, string? binErr, _) = new SonicBinRestartExecutor(conn).Probe();
|
||||
string binDisplay = binOk ? "stop/startcontainer=ok" : $"bin=fehlt ({binErr})";
|
||||
string baseText =
|
||||
$"{conn.ManagementModeDisplay} | SonicHome={sonicHome} | {javaDisplay}";
|
||||
$"{conn.ManagementModeDisplay} | SonicHome={sonicHome} | {binDisplay}";
|
||||
if (!string.IsNullOrWhiteSpace(suffix))
|
||||
{
|
||||
baseText = $"{suffix} | {baseText}";
|
||||
}
|
||||
|
||||
lblSonicStatus.Text = baseText;
|
||||
lblSonicStatus.ForeColor = string.IsNullOrWhiteSpace(javaExe) ? GoldColor : GreenColor;
|
||||
lblSonicStatus.ForeColor = binOk ? GreenColor : GoldColor;
|
||||
}
|
||||
|
||||
private static string TruncateStatus(string? text, int max = 120)
|
||||
|
||||
@@ -42,7 +42,11 @@ public sealed class SonicConnection
|
||||
/// WinRm / LocalCmd = optionaler Fallback über stopcontainer/startcontainer;
|
||||
/// HttpApi = REST (falls vorhanden).
|
||||
/// </summary>
|
||||
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.MfApi;
|
||||
/// <summary>
|
||||
/// Standard: LocalCmd = stopcontainer/startcontainer unter SonicHome (laut Sonic-Doku, ohne extra Java).
|
||||
/// Optional: MfApi (Domain Manager via Java-Client) oder WinRm.
|
||||
/// </summary>
|
||||
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.LocalCmd;
|
||||
|
||||
/// <summary>
|
||||
/// Effektiver Modus: bei WinRm und lokalem ConnectionUrl-Host wird LocalCmd erzwungen.
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using ZA.CoreService.ESBCertificateManager.Models;
|
||||
|
||||
namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Neustart über die offiziellen Sonic-Bin-Scripts (laut Doku):
|
||||
/// SonicHome\bin\stopcontainer.bat Domain.Container
|
||||
/// SonicHome\bin\startcontainer.bat Domain.Container
|
||||
/// Die Scripts setzen selbst JAVA_HOME – kein separates System-Java nötig.
|
||||
/// </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 = Path.Combine(sonicHome, "bin", "stopcontainer.bat");
|
||||
string start = Path.Combine(sonicHome, "bin", "startcontainer.bat");
|
||||
if (!File.Exists(stop) || !File.Exists(start))
|
||||
{
|
||||
return (false,
|
||||
$"stopcontainer.bat/startcontainer.bat fehlen unter '{Path.Combine(sonicHome, "bin")}'.",
|
||||
$"stop={File.Exists(stop)}; start={File.Exists(start)}");
|
||||
}
|
||||
|
||||
return (true, null, $"SonicHome={sonicHome}");
|
||||
}
|
||||
|
||||
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 bin = Path.Combine(sonicHome, "bin");
|
||||
string stopBat = Path.Combine(bin, "stopcontainer.bat");
|
||||
string startBat = Path.Combine(bin, "startcontainer.bat");
|
||||
|
||||
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($"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));
|
||||
|
||||
// stopcontainer kann ExitCode!=0 liefern wenn Container schon down – trotzdem starten
|
||||
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 (!startOk || startCode != 0)
|
||||
{
|
||||
// Fallback: Kurzname ohne Domain
|
||||
if (!string.Equals(shortName, fullName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
log.AppendLine($"Retry START mit Kurzname '{shortName}'…");
|
||||
(startOk, 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());
|
||||
}
|
||||
|
||||
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] + "…";
|
||||
}
|
||||
@@ -35,34 +35,47 @@ public sealed class SonicContainerDiscovery
|
||||
using SonicManagementClient client = new(connection);
|
||||
|
||||
(bool reachable, string? pingError, string? resolvedPath) = await client.CheckConnectionAsync(cancellationToken);
|
||||
if (!reachable)
|
||||
{
|
||||
return SonicDiscoveryResult.Failed(connectionName,
|
||||
$"Management-Konsole nicht erreichbar: {pingError}");
|
||||
}
|
||||
|
||||
(bool listOk, IReadOnlyList<string> remoteNames, string? listError) =
|
||||
await client.GetContainersAsync(cancellationToken);
|
||||
|
||||
List<string> diagnostics = [];
|
||||
List<string> remoteContainers = [];
|
||||
bool listOk = false;
|
||||
string? listError = null;
|
||||
|
||||
foreach (string line in remoteNames)
|
||||
if (!reachable)
|
||||
{
|
||||
if (line.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase)
|
||||
|| line.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase))
|
||||
// Trotzdem KnownContainers nutzen – Neustart kann über SonicBin (stop/startcontainer) gehen.
|
||||
diagnostics.Add($"Management-Ping: {pingError}");
|
||||
if (connection.KnownContainers.Count == 0
|
||||
&& !knownTargets.Any(t =>
|
||||
string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.IsNullOrWhiteSpace(t.ContainerName)))
|
||||
{
|
||||
diagnostics.Add(line);
|
||||
continue;
|
||||
return SonicDiscoveryResult.Failed(connectionName,
|
||||
$"Management-Konsole nicht erreichbar: {pingError}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
(listOk, IReadOnlyList<string> remoteNames, listError) =
|
||||
await client.GetContainersAsync(cancellationToken);
|
||||
|
||||
foreach (string line in remoteNames)
|
||||
{
|
||||
if (line.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase)
|
||||
|| line.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
diagnostics.Add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
remoteContainers.Add(line);
|
||||
}
|
||||
|
||||
remoteContainers.Add(line);
|
||||
}
|
||||
|
||||
if (!listOk)
|
||||
{
|
||||
diagnostics.Add($"Listen-Fehler: {listError}");
|
||||
remoteContainers = [];
|
||||
if (!listOk)
|
||||
{
|
||||
diagnostics.Add($"Listen-Fehler: {listError}");
|
||||
remoteContainers = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: explizit in appsettings hinterlegte Container
|
||||
|
||||
@@ -95,18 +95,49 @@ public sealed class SonicManagementClient : IDisposable
|
||||
if (UsesMfApi)
|
||||
{
|
||||
(bool ok, string? error) = await _mfApi!.TestConnectionAsync(cancellationToken);
|
||||
string modeLabel =
|
||||
$"MfApi {_connection.ConnectionUrl} Domain={_connection.DomainName}";
|
||||
return (ok, error, ok ? modeLabel : null);
|
||||
if (ok)
|
||||
{
|
||||
return (true, null,
|
||||
$"MfApi {_connection.ConnectionUrl} Domain={_connection.DomainName}");
|
||||
}
|
||||
|
||||
// Ohne lokales Java: Domain-Manager-TCP + KnownContainers reichen für Discovery.
|
||||
bool javaMissing = error?.Contains("Java nicht gefunden", StringComparison.OrdinalIgnoreCase) == true;
|
||||
if (javaMissing)
|
||||
{
|
||||
SonicBinRestartExecutor bin = new(_connection);
|
||||
(bool binOk, _, string? binDetail) = bin.Probe();
|
||||
if (binOk)
|
||||
{
|
||||
return (true, null,
|
||||
$"SonicBin Fallback (kein Java für MfApi) {binDetail}; {_connection.ConnectionUrl}");
|
||||
}
|
||||
|
||||
if (_connection.KnownContainers.Count > 0
|
||||
|| await IsTcpReachableAsync(_connection.ConnectionUrl, cancellationToken))
|
||||
{
|
||||
return (true, null,
|
||||
$"MfApi ohne Java – KnownContainers/TCP; Hinweis: {Truncate(error ?? string.Empty, 180)}");
|
||||
}
|
||||
}
|
||||
|
||||
return (false, error, null);
|
||||
}
|
||||
|
||||
if (UsesScripts)
|
||||
{
|
||||
if (Mode == SonicManagementMode.LocalCmd)
|
||||
{
|
||||
(bool binOk, string? binError, string? binDetail) = new SonicBinRestartExecutor(_connection).Probe();
|
||||
return binOk
|
||||
? (true, null, $"LocalCmd/SonicBin {binDetail}")
|
||||
: (false, binError, null);
|
||||
}
|
||||
|
||||
(bool ok, string? error) = await _scriptRunner!.TestConnectionAsync(cancellationToken);
|
||||
string modeLabel = Mode == SonicManagementMode.LocalCmd
|
||||
? $"LocalCmd (SonicHome={_connection.SonicHome})"
|
||||
: $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}";
|
||||
return (ok, error, ok ? modeLabel : null);
|
||||
return ok
|
||||
? (true, null, $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}")
|
||||
: (false, error, null);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -169,6 +200,13 @@ public sealed class SonicManagementClient : IDisposable
|
||||
|
||||
if (UsesScripts)
|
||||
{
|
||||
// LocalCmd / WinRm: bevorzugter Doku-Weg über die .bat-Dateien (ohne extra Java-Suche).
|
||||
if (Mode == SonicManagementMode.LocalCmd)
|
||||
{
|
||||
return await new SonicBinRestartExecutor(_connection)
|
||||
.RestartAsync(containerName, cancellationToken);
|
||||
}
|
||||
|
||||
return await RestartViaScriptAsync(containerName, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -260,22 +298,60 @@ public sealed class SonicManagementClient : IDisposable
|
||||
(bool ok, string? output, string? error) =
|
||||
await _mfApi!.RestartAsync(containerName, cancellationToken);
|
||||
|
||||
if (!ok)
|
||||
if (ok)
|
||||
{
|
||||
return (false, "Neustart fehlgeschlagen (MfApi)",
|
||||
$"IAgentProxy.restart für '{containerName}' (Domain '{_connection.DomainName}') " +
|
||||
$"über {_connection.ConnectionUrl} fehlgeschlagen.\n" +
|
||||
$"Fehler: {error}\nAusgabe: {output}\n" +
|
||||
"Voraussetzungen: Domain Manager erreichbar, SMC-Logins korrekt, " +
|
||||
"JRE (JavaHome/SonicHome\\jre/JAVA_HOME) + Sonic-Client-JARs (SonicHome\\lib), Container online.");
|
||||
await Task.Delay(
|
||||
TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)),
|
||||
cancellationToken);
|
||||
|
||||
return (true, $"Container '{containerName}' neugestartet (MfApi)",
|
||||
$"Domain={_connection.DomainName}; ConnectionUrl={_connection.ConnectionUrl}\n{output}");
|
||||
}
|
||||
|
||||
await Task.Delay(
|
||||
TimeSpan.FromSeconds(Math.Clamp(_connection.PostRestartDelaySeconds, 0, 120)),
|
||||
cancellationToken);
|
||||
// Kein Java / keine Client-JARs → offizieller Doku-Weg über stop/startcontainer.bat
|
||||
bool softFail = error?.Contains("Java nicht gefunden", StringComparison.OrdinalIgnoreCase) == true
|
||||
|| error?.Contains("Client-JARs", StringComparison.OrdinalIgnoreCase) == true
|
||||
|| error?.Contains("lib", StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
return (true, $"Container '{containerName}' neugestartet (MfApi)",
|
||||
$"Domain={_connection.DomainName}; ConnectionUrl={_connection.ConnectionUrl}\n{output}");
|
||||
if (softFail)
|
||||
{
|
||||
SonicBinRestartExecutor bin = new(_connection);
|
||||
(bool binOk, string binStatus, string? binDetail) =
|
||||
await bin.RestartAsync(containerName, cancellationToken);
|
||||
if (binOk)
|
||||
{
|
||||
return (true, binStatus,
|
||||
$"MfApi nicht nutzbar ({Truncate(error ?? string.Empty, 160)}) – Fallback SonicBin:\n{binDetail}");
|
||||
}
|
||||
|
||||
return (false, "Neustart fehlgeschlagen (MfApi→SonicBin)",
|
||||
$"MfApi: {error}\n\nSonicBin-Fallback: {binDetail}\n" +
|
||||
$"SonicHome='{_connection.SonicHome}' muss stopcontainer.bat enthalten.");
|
||||
}
|
||||
|
||||
return (false, "Neustart fehlgeschlagen (MfApi)",
|
||||
$"IAgentProxy.restart für '{containerName}' (Domain '{_connection.DomainName}') " +
|
||||
$"über {_connection.ConnectionUrl} fehlgeschlagen.\n" +
|
||||
$"Fehler: {error}\nAusgabe: {output}");
|
||||
}
|
||||
|
||||
private static async Task<bool> IsTcpReachableAsync(string connectionUrl, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri uri = new(connectionUrl);
|
||||
string host = uri.Host;
|
||||
int port = uri.Port > 0 ? uri.Port : 2506;
|
||||
using System.Net.Sockets.TcpClient tcp = new();
|
||||
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
await tcp.ConnectAsync(host, port, cts.Token);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static SonicConnection CloneAsLocalCmd(SonicConnection source)
|
||||
|
||||
@@ -10,15 +10,9 @@
|
||||
"Name": "DE-Test",
|
||||
"DomainName": "proalpha-test",
|
||||
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
||||
// Sonic SMC / Domain Manager (für MfApi-Neustart):
|
||||
"Username": "Administrator",
|
||||
"Password": "Administrator",
|
||||
// Standard: Sonic MF Management API (IAgentProxy.restart) – kein WinRM.
|
||||
// Benötigt JRE + Client-JARs unter SonicHome\\lib (oder MfClientLibPath / MQ_HOME).
|
||||
// Java-Suche: JavaPath/JavaHome, JAVA_HOME/JRE_HOME, setenv.bat, rekursiv unter SonicHome
|
||||
// (nicht nur SonicHome\\jre\\bin\\java.exe), Registry, where.exe. Optional JavaHome/JavaPath setzen.
|
||||
// Optional-Fallback: "WinRm" / "LocalCmd" (stopcontainer/startcontainer).
|
||||
"ManagementMode": "MfApi",
|
||||
"ManagementMode": "LocalCmd",
|
||||
"SonicHome": "C:\\Sonic\\MQ10.0",
|
||||
"JavaHome": "",
|
||||
"JavaPath": "",
|
||||
@@ -26,7 +20,6 @@
|
||||
"KnownContainers": [
|
||||
"DE-Test"
|
||||
],
|
||||
// Nur für ManagementMode=WinRm relevant; leer lassen wenn nicht genutzt:
|
||||
"WinRmUsername": "",
|
||||
"WinRmPassword": "",
|
||||
"WinRmPort": 5985,
|
||||
|
||||
Reference in New Issue
Block a user