Files
123123/ZA.CoreService.ESBCertificateManager/Services/SonicMfApiExecutor.cs
T
GizzlerandCursor c73f53d740 Slim MfApi stack to restart-only path.
Drop ping/list discovery, WinRM scripts, and heavy Java probing so only KnownContainers + MBean restart remain.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 12:32:05 +02:00

280 lines
9.1 KiB
C#

using System.Diagnostics;
using System.Text;
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Services;
/// <summary>
/// Startet nur den Container-Restart über SonicMfContainerTool (Java + MF-Client-JARs).
/// </summary>
public sealed class SonicMfApiExecutor
{
private readonly SonicConnection _connection;
public SonicMfApiExecutor(SonicConnection connection)
{
_connection = connection;
}
public (string SonicHome, string? JavaExe) ResolveRuntimePaths()
{
string sonicHome = string.IsNullOrWhiteSpace(_connection.SonicHome)
? "(leer)"
: _connection.SonicHome.Trim();
return (sonicHome, ResolveJavaExe());
}
public async Task<(bool Success, string? Output, string? Error)> RestartAsync(
string containerName,
CancellationToken cancellationToken = default)
{
(bool prepared, string? javaExe, string? classDir, string? classpath, string? prepareError) =
PrepareTool();
if (!prepared)
{
return (false, null, prepareError);
}
ProcessStartInfo psi = new()
{
FileName = javaExe!,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
WorkingDirectory = classDir!
};
psi.ArgumentList.Add("-cp");
psi.ArgumentList.Add(classpath!);
psi.ArgumentList.Add("SonicMfContainerTool");
psi.ArgumentList.Add("restart");
psi.ArgumentList.Add("--domain");
psi.ArgumentList.Add(_connection.DomainName);
psi.ArgumentList.Add("--url");
psi.ArgumentList.Add(_connection.ConnectionUrl);
psi.ArgumentList.Add("--user");
psi.ArgumentList.Add(_connection.Username);
psi.ArgumentList.Add("--container");
psi.ArgumentList.Add(containerName);
psi.ArgumentList.Add("--timeout");
psi.ArgumentList.Add(Math.Clamp(_connection.TimeoutSeconds, 5, 600).ToString());
psi.Environment["ESB_SONIC_PASSWORD"] = _connection.Password ?? string.Empty;
using Process process = new() { StartInfo = psi };
if (!process.Start())
{
return (false, null, "Java-Prozess konnte nicht gestartet werden.");
}
using CancellationTokenSource timeoutCts =
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(Math.Clamp(_connection.TimeoutSeconds, 10, 600)));
Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync(cancellationToken);
Task<string> stderrTask = process.StandardError.ReadToEndAsync(cancellationToken);
try
{
await process.WaitForExitAsync(timeoutCts.Token);
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
try { process.Kill(entireProcessTree: true); } catch { /* ignore */ }
return (false, null, $"MfApi Timeout nach {_connection.TimeoutSeconds}s.");
}
string stdout = (await stdoutTask).Trim();
string stderr = (await stderrTask).Trim();
string combined = string.Join("\n", new[] { stdout, stderr }.Where(s => s.Length > 0));
bool ok = process.ExitCode == 0
&& combined.Contains("OK:RestartInvoked", StringComparison.OrdinalIgnoreCase)
&& !combined.Contains("ERROR:", StringComparison.OrdinalIgnoreCase);
if (ok)
{
return (true, combined, null);
}
string err = ExtractError(combined)
?? $"SonicMfContainerTool ExitCode={process.ExitCode}";
if (err.Contains("ClassNotFoundException", StringComparison.OrdinalIgnoreCase)
|| combined.Contains("JMSConnectorAddress", StringComparison.OrdinalIgnoreCase))
{
err +=
"\n\nClasspath unvollständig. MfClientLibPath auf Ordner mit mgmt_client.jar / mfcontext.jar setzen.";
}
string detail = string.IsNullOrWhiteSpace(combined) ? err : err + "\n\n--- Tool-Output ---\n" + combined;
return (false, combined.Length > 0 ? combined : null, detail);
}
private (bool Ok, string? JavaExe, string? ClassDir, string? Classpath, string? Error) PrepareTool()
{
string? javaExe = ResolveJavaExe();
if (javaExe is null)
{
return (false, null, null, null,
"Java nicht gefunden. In appsettings JavaPath auf java.exe (Java 8+) setzen.");
}
(string? libClasspath, string? libError) = ResolveSonicClasspath();
if (libClasspath is null)
{
return (false, null, null, null, libError);
}
string? toolsDir = ResolveToolsDir();
if (toolsDir is null)
{
return (false, null, null, null,
"Tools\\SonicMfContainerTool.class nicht gefunden. Projekt neu bauen.");
}
string classpath = toolsDir + Path.PathSeparator + libClasspath;
return (true, javaExe, toolsDir, classpath, null);
}
private string? ResolveJavaExe()
{
foreach (string? candidate in EnumerateJavaCandidates())
{
if (!string.IsNullOrWhiteSpace(candidate) && File.Exists(candidate))
{
return Path.GetFullPath(candidate);
}
}
return null;
}
private IEnumerable<string?> EnumerateJavaCandidates()
{
if (!string.IsNullOrWhiteSpace(_connection.JavaPath))
{
yield return _connection.JavaPath.Trim();
}
if (!string.IsNullOrWhiteSpace(_connection.JavaHome))
{
yield return Path.Combine(_connection.JavaHome.Trim(), "bin", "java.exe");
}
string? javaHome = Environment.GetEnvironmentVariable("JAVA_HOME");
if (!string.IsNullOrWhiteSpace(javaHome))
{
yield return Path.Combine(javaHome.Trim(), "bin", "java.exe");
}
string? jreHome = Environment.GetEnvironmentVariable("JRE_HOME");
if (!string.IsNullOrWhiteSpace(jreHome))
{
yield return Path.Combine(jreHome.Trim(), "bin", "java.exe");
}
yield return "java.exe"; // PATH
}
private (string? Classpath, string? Error) ResolveSonicClasspath()
{
List<string> libDirs = [];
if (!string.IsNullOrWhiteSpace(_connection.MfClientLibPath))
{
libDirs.Add(_connection.MfClientLibPath.Trim());
}
if (!string.IsNullOrWhiteSpace(_connection.SonicHome))
{
string home = _connection.SonicHome.Trim();
libDirs.Add(Path.Combine(home, "lib"));
libDirs.Add(home);
}
HashSet<string> jars = new(StringComparer.OrdinalIgnoreCase);
foreach (string dir in libDirs.Where(Directory.Exists))
{
foreach (string jar in Directory.EnumerateFiles(dir, "*.jar", SearchOption.TopDirectoryOnly))
{
jars.Add(jar);
}
}
if (jars.Count == 0)
{
return (null,
"Keine Sonic-Client-JARs gefunden. MfClientLibPath oder SonicHome\\lib setzen "
+ "(mgmt_client.jar, mfcontext.jar, …).");
}
// Explizite Kern-JARs zuerst, falls vorhanden
string[] preferred =
[
"mgmt_client.jar",
"mfcontext.jar",
"sonic_Client.jar",
"sonic_Crypto.jar",
"mf_common.jar"
];
List<string> ordered = [];
foreach (string name in preferred)
{
string? hit = jars.FirstOrDefault(j =>
string.Equals(Path.GetFileName(j), name, StringComparison.OrdinalIgnoreCase));
if (hit is not null)
{
ordered.Add(hit);
}
}
foreach (string jar in jars.OrderBy(Path.GetFileName, StringComparer.OrdinalIgnoreCase))
{
if (!ordered.Contains(jar, StringComparer.OrdinalIgnoreCase))
{
ordered.Add(jar);
}
}
return (string.Join(Path.PathSeparator, ordered), null);
}
private static string? ResolveToolsDir()
{
string[] dirs =
[
Path.Combine(AppContext.BaseDirectory, "Tools"),
Path.Combine(Directory.GetCurrentDirectory(), "Tools"),
AppContext.BaseDirectory
];
foreach (string dir in dirs.Where(Directory.Exists))
{
if (File.Exists(Path.Combine(dir, "SonicMfContainerTool.class")))
{
return dir;
}
}
return null;
}
private static string? ExtractError(string combined)
{
foreach (string line in combined.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries))
{
if (line.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase))
{
return line["ERROR:".Length..].Trim();
}
}
return null;
}
}