Drop ping/list discovery, WinRM scripts, and heavy Java probing so only KnownContainers + MBean restart remain. Co-authored-by: Cursor <cursoragent@cursor.com>
280 lines
9.1 KiB
C#
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;
|
|
}
|
|
}
|