Switch default restart to Sonic MF Management API via Domain Manager.
Use ConnectionUrl and SMC credentials with IAgentProxy.restart; keep WinRM only as optional fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1809,7 +1809,7 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
{
|
{
|
||||||
SonicConnection first = _sonicDiscovery.Connections[0];
|
SonicConnection first = _sonicDiscovery.Connections[0];
|
||||||
lblSonicStatus.Text =
|
lblSonicStatus.Text =
|
||||||
$"{_sonicDiscovery.Connections.Count} Verb. | {first.ManagementMode} | {first.DomainName} | SonicHome={first.SonicHome}";
|
$"{_sonicDiscovery.Connections.Count} Verb. | {first.ManagementModeDisplay} | {first.DomainName} | {first.ConnectionUrl}";
|
||||||
lblSonicStatus.ForeColor = GreenColor;
|
lblSonicStatus.ForeColor = GreenColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,15 +38,104 @@ public sealed class SonicConnection
|
|||||||
public string WinRmPassword { get; init; } = string.Empty;
|
public string WinRmPassword { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WinRm = remote via PowerShell Remoting;
|
/// MfApi = Sonic MF Management Runtime API über ConnectionUrl + SMC-Logins (Standard);
|
||||||
/// LocalCmd = lokal auf dem Sonic-Server (kein WinRM, App muss dort laufen);
|
/// WinRm / LocalCmd = optionaler Fallback über stopcontainer/startcontainer;
|
||||||
/// HttpApi = REST.
|
/// HttpApi = REST (falls vorhanden).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.WinRm;
|
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.MfApi;
|
||||||
|
|
||||||
/// <summary>Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0".</summary>
|
/// <summary>
|
||||||
|
/// Effektiver Modus: bei WinRm und lokalem ConnectionUrl-Host wird LocalCmd erzwungen.
|
||||||
|
/// MfApi bleibt unverändert (nutzt Domain-Manager-Verbindung).
|
||||||
|
/// </summary>
|
||||||
|
public SonicManagementMode EffectiveManagementMode
|
||||||
|
=> ManagementMode == SonicManagementMode.WinRm && IsConnectionHostLocal(ConnectionUrl)
|
||||||
|
? SonicManagementMode.LocalCmd
|
||||||
|
: ManagementMode;
|
||||||
|
|
||||||
|
/// <summary>True wenn WinRm konfiguriert war, aber wegen lokalem Host auf LocalCmd umgestellt wurde.</summary>
|
||||||
|
public bool IsLocalCmdAutoForced
|
||||||
|
=> ManagementMode == SonicManagementMode.WinRm
|
||||||
|
&& EffectiveManagementMode == SonicManagementMode.LocalCmd;
|
||||||
|
|
||||||
|
/// <summary>Anzeigetext für UI (inkl. Auto-Erkennung).</summary>
|
||||||
|
public string ManagementModeDisplay
|
||||||
|
=> IsLocalCmdAutoForced
|
||||||
|
? "LocalCmd (Host lokal erkannt)"
|
||||||
|
: EffectiveManagementMode switch
|
||||||
|
{
|
||||||
|
SonicManagementMode.MfApi => "MfApi (Sonic Domain Manager)",
|
||||||
|
_ => EffectiveManagementMode.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True wenn der Host aus ConnectionUrl dieser Maschine entspricht
|
||||||
|
/// (localhost / 127.0.0.1 / ::1 / Computername).
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsConnectionHostLocal(string? connectionUrl)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionUrl))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string host;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
host = new Uri(connectionUrl).Host;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
host = connectionUrl.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(host))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| host.Equals("::1", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| host.Equals("[::1]", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string machine = Environment.MachineName;
|
||||||
|
if (host.Equals(machine, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| host.StartsWith(machine + ".", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dnsName = System.Net.Dns.GetHostName();
|
||||||
|
if (!string.IsNullOrWhiteSpace(dnsName)
|
||||||
|
&& (host.Equals(dnsName, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| host.StartsWith(dnsName + ".", StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// DNS optional
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0" (enthält lib\*.jar für MfApi).</summary>
|
||||||
public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0";
|
public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optionaler Pfad zu Sonic-Client-JARs für MfApi (mgmt_client.jar etc.).
|
||||||
|
/// Leer = SonicHome\lib.
|
||||||
|
/// </summary>
|
||||||
|
public string MfClientLibPath { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Container-Namen (kurz), z.B. ["DE-Test"]. Nicht die Domain.
|
/// Container-Namen (kurz), z.B. ["DE-Test"]. Nicht die Domain.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -281,5 +370,7 @@ public enum SonicManagementMode
|
|||||||
{
|
{
|
||||||
HttpApi = 0,
|
HttpApi = 0,
|
||||||
WinRm = 1,
|
WinRm = 1,
|
||||||
LocalCmd = 2
|
LocalCmd = 2,
|
||||||
|
/// <summary>Sonic MF Management API über Domain-Manager (ConnectionUrl + SMC-Credentials).</summary>
|
||||||
|
MfApi = 3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,12 @@ namespace ZA.CoreService.ESBCertificateManager.Services;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verwaltet Sonic ESB Container über die Management Console.
|
/// Verwaltet Sonic ESB Container über die Management Console.
|
||||||
///
|
///
|
||||||
/// Modus = WinRm (Standard):
|
/// Modus = MfApi (Standard):
|
||||||
/// PowerShell Remoting (Invoke-Command) → CMD stop/startcontainer auf dem Sonic-Server.
|
/// Offizielle Sonic MF Management Runtime API über ConnectionUrl + SMC-Logins
|
||||||
/// WinRM nutzt WinRmUsername/WinRmPassword (Windows); leer = aktueller Benutzer.
|
/// (JMSConnectorClient → MFProxyFactory.createAgentProxy → IAgentProxy.restart).
|
||||||
/// Username/Password bleiben Sonic-SMC-/Domain-Manager-Logins.
|
|
||||||
///
|
///
|
||||||
/// Modus = LocalCmd:
|
/// Modus = WinRm / LocalCmd (optionaler Fallback):
|
||||||
/// Dieselbe CMD/PowerShell-Logik lokal (App muss auf dem Sonic-PC laufen; kein WinRM).
|
/// PowerShell/CMD stopcontainer/startcontainer (nicht Domain-Manager-nativ).
|
||||||
///
|
///
|
||||||
/// Modus = HttpApi:
|
/// Modus = HttpApi:
|
||||||
/// HTTP REST API mit automatischer Pfad-Erkennung.
|
/// HTTP REST API mit automatischer Pfad-Erkennung.
|
||||||
@@ -25,6 +24,7 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
private readonly SonicConnection _connection;
|
private readonly SonicConnection _connection;
|
||||||
private readonly HttpClient? _http;
|
private readonly HttpClient? _http;
|
||||||
private readonly WinRmExecutor? _scriptRunner;
|
private readonly WinRmExecutor? _scriptRunner;
|
||||||
|
private readonly SonicMfApiExecutor? _mfApi;
|
||||||
|
|
||||||
// Gecachter HTTP-Basis-Pfad (nur im HttpApi-Modus)
|
// Gecachter HTTP-Basis-Pfad (nur im HttpApi-Modus)
|
||||||
private string? _resolvedContainerBasePath;
|
private string? _resolvedContainerBasePath;
|
||||||
@@ -37,14 +37,22 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
"/containers"
|
"/containers"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private SonicManagementMode Mode => _connection.EffectiveManagementMode;
|
||||||
|
|
||||||
|
private bool UsesMfApi => Mode == SonicManagementMode.MfApi;
|
||||||
|
|
||||||
private bool UsesScripts =>
|
private bool UsesScripts =>
|
||||||
_connection.ManagementMode is SonicManagementMode.WinRm or SonicManagementMode.LocalCmd;
|
Mode is SonicManagementMode.WinRm or SonicManagementMode.LocalCmd;
|
||||||
|
|
||||||
public SonicManagementClient(SonicConnection connection)
|
public SonicManagementClient(SonicConnection connection)
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
|
|
||||||
if (UsesScripts)
|
if (UsesMfApi)
|
||||||
|
{
|
||||||
|
_mfApi = new SonicMfApiExecutor(connection);
|
||||||
|
}
|
||||||
|
else if (UsesScripts)
|
||||||
{
|
{
|
||||||
_scriptRunner = new WinRmExecutor(connection);
|
_scriptRunner = new WinRmExecutor(connection);
|
||||||
}
|
}
|
||||||
@@ -77,16 +85,25 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prüft die Verbindung zur Management Console.
|
/// Prüft die Verbindung zur Management Console.
|
||||||
/// WinRm: TCP-Ping auf WinRM-Port + Test-PSSession.
|
/// MfApi: Domain-Manager über ConnectionUrl + SMC-Credentials.
|
||||||
|
/// WinRm/LocalCmd: Script-Laufzeit.
|
||||||
/// Http: Probe gegen bekannte API-Pfade.
|
/// Http: Probe gegen bekannte API-Pfade.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<(bool Success, string? Error, string? ResolvedPath)> CheckConnectionAsync(
|
public async Task<(bool Success, string? Error, string? ResolvedPath)> CheckConnectionAsync(
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
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 (UsesScripts)
|
if (UsesScripts)
|
||||||
{
|
{
|
||||||
(bool ok, string? error) = await _scriptRunner!.TestConnectionAsync(cancellationToken);
|
(bool ok, string? error) = await _scriptRunner!.TestConnectionAsync(cancellationToken);
|
||||||
string modeLabel = _connection.ManagementMode == SonicManagementMode.LocalCmd
|
string modeLabel = Mode == SonicManagementMode.LocalCmd
|
||||||
? $"LocalCmd (SonicHome={_connection.SonicHome})"
|
? $"LocalCmd (SonicHome={_connection.SonicHome})"
|
||||||
: $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}";
|
: $"WinRM auf {ExtractHost(_connection.ConnectionUrl)}:{_connection.WinRmPort}";
|
||||||
return (ok, error, ok ? modeLabel : null);
|
return (ok, error, ok ? modeLabel : null);
|
||||||
@@ -100,7 +117,7 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
return (false,
|
return (false,
|
||||||
$"Keine Sonic HTTP-API unter {_http!.BaseAddress} gefunden.\n" +
|
$"Keine Sonic HTTP-API unter {_http!.BaseAddress} gefunden.\n" +
|
||||||
$"Geprüfte Pfade: {string.Join(", ", GetCandidatePaths())}\n" +
|
$"Geprüfte Pfade: {string.Join(", ", GetCandidatePaths())}\n" +
|
||||||
$"Tipp: ManagementMode auf 'WinRm' setzen falls kein HTTP-API vorhanden.",
|
$"Tipp: ManagementMode auf 'MfApi' setzen (ConnectionUrl + SMC-Logins).",
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +127,7 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
{
|
{
|
||||||
return (false,
|
return (false,
|
||||||
$"HTTP-Timeout nach {_connection.TimeoutSeconds}s – Port {_connection.ManagementHttpPort} nicht erreichbar.\n" +
|
$"HTTP-Timeout nach {_connection.TimeoutSeconds}s – Port {_connection.ManagementHttpPort} nicht erreichbar.\n" +
|
||||||
$"Tipp: ManagementMode auf 'WinRm' setzen.",
|
$"Tipp: ManagementMode auf 'MfApi' setzen.",
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -125,6 +142,11 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
public async Task<(bool Success, IReadOnlyList<string> ContainerNames, string? Error)> GetContainersAsync(
|
public async Task<(bool Success, IReadOnlyList<string> ContainerNames, string? Error)> GetContainersAsync(
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (UsesMfApi)
|
||||||
|
{
|
||||||
|
return await GetContainersViaMfApiAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
if (UsesScripts)
|
if (UsesScripts)
|
||||||
{
|
{
|
||||||
return await GetContainersViaScriptAsync(cancellationToken);
|
return await GetContainersViaScriptAsync(cancellationToken);
|
||||||
@@ -140,6 +162,11 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
string containerName,
|
string containerName,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (UsesMfApi)
|
||||||
|
{
|
||||||
|
return await RestartViaMfApiAsync(containerName, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
if (UsesScripts)
|
if (UsesScripts)
|
||||||
{
|
{
|
||||||
return await RestartViaScriptAsync(containerName, cancellationToken);
|
return await RestartViaScriptAsync(containerName, cancellationToken);
|
||||||
@@ -156,6 +183,41 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
string xapiSourcePath,
|
string xapiSourcePath,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (UsesMfApi)
|
||||||
|
{
|
||||||
|
// XApi-Import bleibt script/HTTP; Neustart danach über MfApi.
|
||||||
|
if (!string.IsNullOrWhiteSpace(_connection.WinRmXapiImportScript))
|
||||||
|
{
|
||||||
|
WinRmExecutor local = new(CloneAsLocalCmd(_connection));
|
||||||
|
string script = WinRmExecutor.ApplyScriptTemplate(
|
||||||
|
_connection.WinRmXapiImportScript,
|
||||||
|
containerName,
|
||||||
|
_connection.DomainName,
|
||||||
|
_connection.SonicHome,
|
||||||
|
xapiSourcePath,
|
||||||
|
_connection.ConnectionUrl,
|
||||||
|
_connection.Username,
|
||||||
|
_connection.Password);
|
||||||
|
(bool importOk, string? importOut, string? importErr) =
|
||||||
|
await local.RunScriptAsync(script, cancellationToken);
|
||||||
|
if (!importOk)
|
||||||
|
{
|
||||||
|
return (false, "XApi-Import fehlgeschlagen", importErr ?? importOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
(bool restartOk, string restartStatus, string? restartDetail) =
|
||||||
|
await RestartViaMfApiAsync(containerName, cancellationToken);
|
||||||
|
return restartOk
|
||||||
|
? (true, "XApi importiert + Container neugestartet (MfApi)",
|
||||||
|
$"Import: {importOut} | Restart: {restartDetail}")
|
||||||
|
: (false, restartStatus, restartDetail);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, "XApi-Import fehlgeschlagen",
|
||||||
|
"Im MfApi-Modus ist WinRmXapiImportScript für den Import nötig, " +
|
||||||
|
"oder ManagementMode vorübergehend auf LocalCmd/WinRm setzen.");
|
||||||
|
}
|
||||||
|
|
||||||
if (UsesScripts)
|
if (UsesScripts)
|
||||||
{
|
{
|
||||||
return await ImportXapiViaScriptAsync(containerName, xapiSourcePath, cancellationToken);
|
return await ImportXapiViaScriptAsync(containerName, xapiSourcePath, cancellationToken);
|
||||||
@@ -164,6 +226,86 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
return await ImportXapiViaHttpAsync(containerName, xapiSourcePath, cancellationToken);
|
return await ImportXapiViaHttpAsync(containerName, xapiSourcePath, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// MfApi (Sonic Domain Manager / IAgentProxy.restart)
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
private async Task<(bool, IReadOnlyList<string>, string?)> GetContainersViaMfApiAsync(
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
(bool ok, IReadOnlyList<string> names, string? error) =
|
||||||
|
await _mfApi!.ListContainersAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
return (false, [], $"Container-Liste fehlgeschlagen (MfApi): {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (names.Count == 0 && _connection.KnownContainers.Count > 0)
|
||||||
|
{
|
||||||
|
List<string> withHint =
|
||||||
|
[
|
||||||
|
..names,
|
||||||
|
$"INFO:MfApiListeLeer FallbackKnownContainers={_connection.KnownContainers.Count}"
|
||||||
|
];
|
||||||
|
return (true, withHint, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true, names, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(bool, string, string?)> RestartViaMfApiAsync(
|
||||||
|
string containerName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
(bool ok, string? output, string? error) =
|
||||||
|
await _mfApi!.RestartAsync(containerName, cancellationToken);
|
||||||
|
|
||||||
|
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, " +
|
||||||
|
"Java/JDK + 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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SonicConnection CloneAsLocalCmd(SonicConnection source)
|
||||||
|
=> new()
|
||||||
|
{
|
||||||
|
Name = source.Name,
|
||||||
|
DomainName = source.DomainName,
|
||||||
|
ConnectionUrl = source.ConnectionUrl,
|
||||||
|
Username = source.Username,
|
||||||
|
Password = source.Password,
|
||||||
|
WinRmUsername = source.WinRmUsername,
|
||||||
|
WinRmPassword = source.WinRmPassword,
|
||||||
|
ManagementMode = SonicManagementMode.LocalCmd,
|
||||||
|
SonicHome = source.SonicHome,
|
||||||
|
MfClientLibPath = source.MfClientLibPath,
|
||||||
|
KnownContainers = source.KnownContainers,
|
||||||
|
ManagementHttpPort = source.ManagementHttpPort,
|
||||||
|
ApiBasePath = source.ApiBasePath,
|
||||||
|
ContainerListPath = source.ContainerListPath,
|
||||||
|
ContainerRestartPath = source.ContainerRestartPath,
|
||||||
|
ContainerStopPath = source.ContainerStopPath,
|
||||||
|
ContainerStartPath = source.ContainerStartPath,
|
||||||
|
WinRmPort = source.WinRmPort,
|
||||||
|
WinRmRestartScript = source.WinRmRestartScript,
|
||||||
|
WinRmContainerListScript = source.WinRmContainerListScript,
|
||||||
|
WinRmXapiImportScript = source.WinRmXapiImportScript,
|
||||||
|
TimeoutSeconds = source.TimeoutSeconds,
|
||||||
|
PostRestartDelaySeconds = source.PostRestartDelaySeconds
|
||||||
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// Script-Implementierungen (WinRM / LocalCmd)
|
// Script-Implementierungen (WinRM / LocalCmd)
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -185,7 +327,7 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
|
|
||||||
if (!ok)
|
if (!ok)
|
||||||
{
|
{
|
||||||
return (false, [], $"Container-Liste fehlgeschlagen ({_connection.ManagementMode}): {error}");
|
return (false, [], $"Container-Liste fehlgeschlagen ({Mode}): {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<string> names = (output ?? string.Empty)
|
List<string> names = (output ?? string.Empty)
|
||||||
@@ -213,7 +355,7 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
(bool ok, string? output, string? error) =
|
(bool ok, string? output, string? error) =
|
||||||
await _scriptRunner!.RunScriptAsync(script, cancellationToken);
|
await _scriptRunner!.RunScriptAsync(script, cancellationToken);
|
||||||
|
|
||||||
string mode = _connection.ManagementMode.ToString();
|
string mode = Mode.ToString();
|
||||||
bool verified = (output ?? string.Empty)
|
bool verified = (output ?? string.Empty)
|
||||||
.Contains("OK:ContainerRestartVerified", StringComparison.OrdinalIgnoreCase);
|
.Contains("OK:ContainerRestartVerified", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using ZA.CoreService.ESBCertificateManager.Models;
|
||||||
|
|
||||||
|
namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Führt Sonic-Container-Operationen über die offizielle MF Management Runtime API aus
|
||||||
|
/// (JMSConnectorClient + MFProxyFactory + IAgentProxy.restart), per Java-Hilfsprogramm
|
||||||
|
/// und Client-JARs unter SonicHome/lib bzw. MfClientLibPath.
|
||||||
|
/// Nutzt ConnectionUrl + Username/Password aus appsettings (SMC / Domain Manager).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SonicMfApiExecutor
|
||||||
|
{
|
||||||
|
private readonly SonicConnection _connection;
|
||||||
|
|
||||||
|
public SonicMfApiExecutor(SonicConnection connection)
|
||||||
|
{
|
||||||
|
_connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<(bool Success, string? Error)> TestConnectionAsync(CancellationToken cancellationToken = default)
|
||||||
|
=> RunAsync("ping", container: null, cancellationToken);
|
||||||
|
|
||||||
|
public async Task<(bool Success, IReadOnlyList<string> Containers, string? Error)> ListContainersAsync(
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
(bool ok, string? output, string? error) = await RunRawAsync("list", null, cancellationToken);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
return (false, [], error ?? output);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> names = (output ?? string.Empty)
|
||||||
|
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||||
|
.Where(l => !l.StartsWith("OK:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(l => !l.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(l => !l.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(l => !l.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(l => !l.StartsWith("Usage:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return (true, names, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(bool Success, string? Output, string? Error)> RestartAsync(
|
||||||
|
string containerName,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
(bool ok, string? output, string? error) = await RunRawAsync("restart", containerName, cancellationToken);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
return (false, output, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool invoked = (output ?? string.Empty)
|
||||||
|
.Contains("OK:RestartInvoked", StringComparison.OrdinalIgnoreCase);
|
||||||
|
return invoked
|
||||||
|
? (true, output, error)
|
||||||
|
: (false, output, error ?? "Kein OK:RestartInvoked von SonicMfContainerTool.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(bool Success, string? Error)> RunAsync(
|
||||||
|
string command,
|
||||||
|
string? container,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
(bool ok, string? output, string? error) = await RunRawAsync(command, container, cancellationToken);
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, error ?? output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(bool Success, string? Output, string? Error)> RunRawAsync(
|
||||||
|
string command,
|
||||||
|
string? container,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
(bool prepared, string? javaExe, string? classDir, string? classpath, string? prepareError) =
|
||||||
|
await PrepareToolAsync(cancellationToken);
|
||||||
|
if (!prepared)
|
||||||
|
{
|
||||||
|
return (false, null, prepareError);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> args =
|
||||||
|
[
|
||||||
|
"-cp", Quote(classpath!),
|
||||||
|
"SonicMfContainerTool",
|
||||||
|
command,
|
||||||
|
"--domain", _connection.DomainName,
|
||||||
|
"--url", _connection.ConnectionUrl,
|
||||||
|
"--user", _connection.Username,
|
||||||
|
"--timeout", Math.Clamp(_connection.TimeoutSeconds, 5, 600).ToString()
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(container))
|
||||||
|
{
|
||||||
|
args.Add("--container");
|
||||||
|
args.Add(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStartInfo psi = new()
|
||||||
|
{
|
||||||
|
FileName = javaExe!,
|
||||||
|
Arguments = string.Join(" ", args.Select(EscapeArg)),
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8,
|
||||||
|
WorkingDirectory = classDir!
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
if (process.ExitCode == 0
|
||||||
|
&& !combined.Contains("ERROR:", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return (true, combined, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
string err = ExtractError(combined)
|
||||||
|
?? $"SonicMfContainerTool ExitCode={process.ExitCode}";
|
||||||
|
return (false, combined.Length > 0 ? combined : null, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(bool Ok, string? JavaExe, string? ClassDir, string? Classpath, string? Error)>
|
||||||
|
PrepareToolAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string? javaExe = ResolveJavaExecutable();
|
||||||
|
if (javaExe is null)
|
||||||
|
{
|
||||||
|
return (false, null, null, null,
|
||||||
|
"Java nicht gefunden (JAVA_HOME/bin/java.exe oder PATH). " +
|
||||||
|
"Für MfApi wird ein JRE/JDK benötigt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string? libDir = ResolveLibDirectory();
|
||||||
|
if (libDir is null)
|
||||||
|
{
|
||||||
|
return (false, null, null, null,
|
||||||
|
"Sonic-Client-JARs nicht gefunden. SonicHome\\lib oder MfClientLibPath setzen " +
|
||||||
|
$"(aktuell SonicHome='{_connection.SonicHome}', MfClientLibPath='{_connection.MfClientLibPath}'). " +
|
||||||
|
"Benötigt u.a. mgmt_client.jar / mfcontext.jar / sonic_Client.jar.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string? sourcePath = ResolveToolSourcePath();
|
||||||
|
if (sourcePath is null)
|
||||||
|
{
|
||||||
|
return (false, null, null, null,
|
||||||
|
"Tools\\SonicMfContainerTool.java nicht gefunden (App-Ausgabeverzeichnis prüfen).");
|
||||||
|
}
|
||||||
|
|
||||||
|
string workDir = Path.Combine(Path.GetTempPath(), "esb-sonic-mf");
|
||||||
|
Directory.CreateDirectory(workDir);
|
||||||
|
|
||||||
|
string javaTarget = Path.Combine(workDir, "SonicMfContainerTool.java");
|
||||||
|
string classFile = Path.Combine(workDir, "SonicMfContainerTool.class");
|
||||||
|
|
||||||
|
File.Copy(sourcePath, javaTarget, overwrite: true);
|
||||||
|
|
||||||
|
string classpath = BuildClasspath(libDir, workDir);
|
||||||
|
bool needsCompile = !File.Exists(classFile)
|
||||||
|
|| File.GetLastWriteTimeUtc(classFile) < File.GetLastWriteTimeUtc(sourcePath);
|
||||||
|
|
||||||
|
if (needsCompile)
|
||||||
|
{
|
||||||
|
string? javac = ResolveJavacExecutable(javaExe);
|
||||||
|
if (javac is null)
|
||||||
|
{
|
||||||
|
return (false, null, null, null,
|
||||||
|
"javac nicht gefunden. JDK installieren (nicht nur JRE), " +
|
||||||
|
"damit SonicMfContainerTool.java kompiliert werden kann.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessStartInfo compilePsi = new()
|
||||||
|
{
|
||||||
|
FileName = javac,
|
||||||
|
Arguments = $"-encoding UTF-8 -cp {Quote(BuildClasspath(libDir, null))} -d {Quote(workDir)} {Quote(javaTarget)}",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8
|
||||||
|
};
|
||||||
|
|
||||||
|
using Process compile = new() { StartInfo = compilePsi };
|
||||||
|
if (!compile.Start())
|
||||||
|
{
|
||||||
|
return (false, null, null, null, "javac konnte nicht gestartet werden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string cOut = await compile.StandardOutput.ReadToEndAsync(cancellationToken);
|
||||||
|
string cErr = await compile.StandardError.ReadToEndAsync(cancellationToken);
|
||||||
|
await compile.WaitForExitAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (compile.ExitCode != 0 || !File.Exists(classFile))
|
||||||
|
{
|
||||||
|
return (false, null, null, null,
|
||||||
|
"Kompilieren von SonicMfContainerTool fehlgeschlagen.\n" +
|
||||||
|
Truncate((cErr + "\n" + cOut).Trim()) +
|
||||||
|
$"\nClasspath-Lib: {libDir}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true, javaExe, workDir, classpath, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ResolveLibDirectory()
|
||||||
|
{
|
||||||
|
List<string> candidates = [];
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(_connection.MfClientLibPath))
|
||||||
|
{
|
||||||
|
candidates.Add(_connection.MfClientLibPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(_connection.SonicHome))
|
||||||
|
{
|
||||||
|
candidates.Add(Path.Combine(_connection.SonicHome, "lib"));
|
||||||
|
candidates.Add(_connection.SonicHome);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string dir in candidates.Where(Directory.Exists))
|
||||||
|
{
|
||||||
|
if (Directory.EnumerateFiles(dir, "*.jar", SearchOption.TopDirectoryOnly).Any())
|
||||||
|
{
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildClasspath(string libDir, string? extraDir)
|
||||||
|
{
|
||||||
|
// Java-Classpath-Wildcard für alle JARs im Lib-Ordner
|
||||||
|
string jars = Path.Combine(libDir, "*");
|
||||||
|
return string.IsNullOrWhiteSpace(extraDir)
|
||||||
|
? jars
|
||||||
|
: jars + Path.PathSeparator + extraDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ResolveToolSourcePath()
|
||||||
|
{
|
||||||
|
string[] candidates =
|
||||||
|
[
|
||||||
|
Path.Combine(AppContext.BaseDirectory, "Tools", "SonicMfContainerTool.java"),
|
||||||
|
Path.Combine(Directory.GetCurrentDirectory(), "Tools", "SonicMfContainerTool.java"),
|
||||||
|
Path.Combine(AppContext.BaseDirectory, "SonicMfContainerTool.java")
|
||||||
|
];
|
||||||
|
|
||||||
|
return candidates.FirstOrDefault(File.Exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ResolveJavaExecutable()
|
||||||
|
{
|
||||||
|
string? javaHome = Environment.GetEnvironmentVariable("JAVA_HOME");
|
||||||
|
if (!string.IsNullOrWhiteSpace(javaHome))
|
||||||
|
{
|
||||||
|
string candidate = Path.Combine(javaHome, "bin", "java.exe");
|
||||||
|
if (File.Exists(candidate))
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FindOnPath("java.exe") ?? FindOnPath("java");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? ResolveJavacExecutable(string javaExe)
|
||||||
|
{
|
||||||
|
string? dir = Path.GetDirectoryName(javaExe);
|
||||||
|
if (!string.IsNullOrWhiteSpace(dir))
|
||||||
|
{
|
||||||
|
string sibling = Path.Combine(dir, OperatingSystem.IsWindows() ? "javac.exe" : "javac");
|
||||||
|
if (File.Exists(sibling))
|
||||||
|
{
|
||||||
|
return sibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string? javaHome = Environment.GetEnvironmentVariable("JAVA_HOME");
|
||||||
|
if (!string.IsNullOrWhiteSpace(javaHome))
|
||||||
|
{
|
||||||
|
string candidate = Path.Combine(javaHome, "bin",
|
||||||
|
OperatingSystem.IsWindows() ? "javac.exe" : "javac");
|
||||||
|
if (File.Exists(candidate))
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FindOnPath("javac.exe") ?? FindOnPath("javac");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? FindOnPath(string fileName)
|
||||||
|
{
|
||||||
|
string? pathEnv = Environment.GetEnvironmentVariable("PATH");
|
||||||
|
if (string.IsNullOrWhiteSpace(pathEnv))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string dir in pathEnv.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string candidate = Path.Combine(dir.Trim('"'), fileName);
|
||||||
|
if (File.Exists(candidate))
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore bad PATH entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 string.IsNullOrWhiteSpace(combined) ? null : Truncate(combined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Quote(string value)
|
||||||
|
=> "\"" + value.Replace("\"", "\\\"") + "\"";
|
||||||
|
|
||||||
|
private static string EscapeArg(string value)
|
||||||
|
{
|
||||||
|
if (value.Length == 0)
|
||||||
|
{
|
||||||
|
return "\"\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needsQuotes = value.Contains(' ') || value.Contains('\t') || value.Contains('"')
|
||||||
|
|| value.Contains('*') || value.Contains(';');
|
||||||
|
if (!needsQuotes)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "\"" + value.Replace("\"", "\\\"") + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Truncate(string s, int max = 800)
|
||||||
|
=> s.Length <= max ? s : s[..max] + "…";
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ public sealed class WinRmExecutor
|
|||||||
public async Task<(bool Success, string? Error)> TestConnectionAsync(
|
public async Task<(bool Success, string? Error)> TestConnectionAsync(
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (_connection.ManagementMode == SonicManagementMode.LocalCmd)
|
if (_connection.EffectiveManagementMode == SonicManagementMode.LocalCmd)
|
||||||
{
|
{
|
||||||
(bool ok, string? output, string? error) = await RunScriptAsync(
|
(bool ok, string? output, string? error) = await RunScriptAsync(
|
||||||
"$env:COMPUTERNAME", cancellationToken);
|
"$env:COMPUTERNAME", cancellationToken);
|
||||||
@@ -66,7 +66,7 @@ public sealed class WinRmExecutor
|
|||||||
string scriptBlock,
|
string scriptBlock,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
string fullScript = _connection.ManagementMode == SonicManagementMode.LocalCmd
|
string fullScript = _connection.EffectiveManagementMode == SonicManagementMode.LocalCmd
|
||||||
? BuildLocalScript(scriptBlock)
|
? BuildLocalScript(scriptBlock)
|
||||||
: BuildRemoteScript(ExtractHost(_connection.ConnectionUrl), scriptBlock);
|
: BuildRemoteScript(ExtractHost(_connection.ConnectionUrl), scriptBlock);
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ public sealed class WinRmExecutor
|
|||||||
? stderr
|
? stderr
|
||||||
: $"PowerShell ExitCode={process.ExitCode}";
|
: $"PowerShell ExitCode={process.ExitCode}";
|
||||||
|
|
||||||
string error = _connection.ManagementMode == SonicManagementMode.WinRm
|
string error = _connection.EffectiveManagementMode == SonicManagementMode.WinRm
|
||||||
? FormatWinRmFailure(rawError)
|
? FormatWinRmFailure(rawError)
|
||||||
: Truncate(rawError);
|
: Truncate(rawError);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.management.ObjectName;
|
||||||
|
|
||||||
|
import com.sonicsw.mf.jmx.client.JMSConnectorAddress;
|
||||||
|
import com.sonicsw.mf.jmx.client.JMSConnectorClient;
|
||||||
|
import com.sonicsw.mf.mgmtapi.runtime.IAgentProxy;
|
||||||
|
import com.sonicsw.mf.mgmtapi.runtime.MFProxyFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sonic MF Management Runtime API helper (Aurea CX Messenger / Progress Sonic).
|
||||||
|
*
|
||||||
|
* Documented path (Management Application API):
|
||||||
|
* Hashtable env with ConnectionURLs / DefaultUser / DefaultPassword
|
||||||
|
* -> JMSConnectorAddress / JMSConnectorClient.connect
|
||||||
|
* -> MFProxyFactory.createAgentProxy(connector, ObjectName)
|
||||||
|
* -> IAgentProxy.restart() (same lifecycle action as SMC Restart)
|
||||||
|
*
|
||||||
|
* ObjectName pattern: {domain}.{container}:ID=AGENT
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* java -cp "...libs...;." SonicMfContainerTool ping|list|restart
|
||||||
|
* --domain proalpha-test
|
||||||
|
* --url tcp://host:13070
|
||||||
|
* --user Administrator
|
||||||
|
* --password *** (or env ESB_SONIC_PASSWORD)
|
||||||
|
* [--container DE-Test] (required for restart)
|
||||||
|
* [--timeout 120]
|
||||||
|
*/
|
||||||
|
public final class SonicMfContainerTool
|
||||||
|
{
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Args a = Args.parse(args);
|
||||||
|
if (a.command == null)
|
||||||
|
{
|
||||||
|
fail("Usage: SonicMfContainerTool ping|list|restart --domain D --url U --user U [--password P] [--container C] [--timeout SEC]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlank(a.domain) || isBlank(a.url) || isBlank(a.user))
|
||||||
|
{
|
||||||
|
fail("domain, url und user sind Pflicht.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("restart".equals(a.command) && isBlank(a.container))
|
||||||
|
{
|
||||||
|
fail("restart erfordert --container.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long timeoutMs = Math.max(5_000L, a.timeoutSec * 1000L);
|
||||||
|
JMSConnectorClient connector = connect(a.url, a.user, a.password, timeoutMs);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ("ping".equals(a.command))
|
||||||
|
{
|
||||||
|
ping(connector, a.domain);
|
||||||
|
}
|
||||||
|
else if ("list".equals(a.command))
|
||||||
|
{
|
||||||
|
list(connector, a.domain);
|
||||||
|
}
|
||||||
|
else if ("restart".equals(a.command))
|
||||||
|
{
|
||||||
|
restart(connector, a.domain, a.container);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fail("Unbekannter Befehl: " + a.command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { connector.disconnect(); } catch (Exception ignore) { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
fail(t.getClass().getSimpleName() + ": " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JMSConnectorClient connect(String url, String user, String password, long timeoutMs)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
Hashtable env = new Hashtable();
|
||||||
|
env.put("ConnectionURLs", url);
|
||||||
|
env.put("DefaultUser", user);
|
||||||
|
env.put("DefaultPassword", password == null ? "" : password);
|
||||||
|
|
||||||
|
JMSConnectorAddress address = new JMSConnectorAddress(env);
|
||||||
|
JMSConnectorClient connector = new JMSConnectorClient();
|
||||||
|
connector.connect(address, timeoutMs);
|
||||||
|
System.out.println("INFO:Connected url=" + url + " user=" + user);
|
||||||
|
return connector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ping(JMSConnectorClient connector, String domain) throws Exception
|
||||||
|
{
|
||||||
|
Set names = connector.queryNames(new ObjectName("*:ID=AGENT"), null);
|
||||||
|
int matched = 0;
|
||||||
|
if (names != null)
|
||||||
|
{
|
||||||
|
for (Iterator it = names.iterator(); it.hasNext(); )
|
||||||
|
{
|
||||||
|
ObjectName on = (ObjectName) it.next();
|
||||||
|
if (belongsToDomain(on, domain))
|
||||||
|
{
|
||||||
|
matched++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println("OK:MfApiPing domain=" + domain + " agents=" + matched);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void list(JMSConnectorClient connector, String domain) throws Exception
|
||||||
|
{
|
||||||
|
Set names = connector.queryNames(new ObjectName("*:ID=AGENT"), null);
|
||||||
|
int count = 0;
|
||||||
|
if (names != null)
|
||||||
|
{
|
||||||
|
for (Iterator it = names.iterator(); it.hasNext(); )
|
||||||
|
{
|
||||||
|
ObjectName on = (ObjectName) it.next();
|
||||||
|
String container = extractContainer(on, domain);
|
||||||
|
if (container != null)
|
||||||
|
{
|
||||||
|
System.out.println(container);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
System.out.println("WARN:KeineAgentContainer Domain=" + domain);
|
||||||
|
}
|
||||||
|
System.out.println("OK:List count=" + count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void restart(JMSConnectorClient connector, String domain, String container) throws Exception
|
||||||
|
{
|
||||||
|
String shortName = shortContainer(container);
|
||||||
|
ObjectName on = resolveAgentObjectName(connector, domain, shortName);
|
||||||
|
System.out.println("INFO:ObjectName=" + on);
|
||||||
|
|
||||||
|
IAgentProxy agent = MFProxyFactory.createAgentProxy(connector, on);
|
||||||
|
String stateBefore = safeState(agent);
|
||||||
|
System.out.println("INFO:StateBefore=" + stateBefore);
|
||||||
|
|
||||||
|
agent.restart();
|
||||||
|
System.out.println("OK:RestartInvoked container=" + shortName + " domain=" + domain
|
||||||
|
+ " stateBefore=" + stateBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectName resolveAgentObjectName(JMSConnectorClient connector, String domain, String container)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
ObjectName preferred = new ObjectName(domain + "." + container + ":ID=AGENT");
|
||||||
|
Set exact = connector.queryNames(preferred, null);
|
||||||
|
if (exact != null && !exact.isEmpty())
|
||||||
|
{
|
||||||
|
return (ObjectName) exact.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set names = connector.queryNames(new ObjectName("*:ID=AGENT"), null);
|
||||||
|
if (names != null)
|
||||||
|
{
|
||||||
|
for (Iterator it = names.iterator(); it.hasNext(); )
|
||||||
|
{
|
||||||
|
ObjectName on = (ObjectName) it.next();
|
||||||
|
String c = extractContainer(on, domain);
|
||||||
|
if (c != null && c.equalsIgnoreCase(container))
|
||||||
|
{
|
||||||
|
return on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: documented canonical name even if not yet in query result
|
||||||
|
return preferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean belongsToDomain(ObjectName on, String domain)
|
||||||
|
{
|
||||||
|
return extractContainer(on, domain) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractContainer(ObjectName on, String domain)
|
||||||
|
{
|
||||||
|
if (on == null || isBlank(domain))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String jmxDomain = on.getDomain();
|
||||||
|
if (jmxDomain == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String prefix = domain + ".";
|
||||||
|
if (jmxDomain.regionMatches(true, 0, prefix, 0, prefix.length()))
|
||||||
|
{
|
||||||
|
return jmxDomain.substring(prefix.length());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String shortContainer(String container)
|
||||||
|
{
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int dot = container.indexOf('.');
|
||||||
|
if (dot >= 0 && dot < container.length() - 1)
|
||||||
|
{
|
||||||
|
return container.substring(dot + 1);
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String safeState(IAgentProxy agent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String s = agent.getStateString();
|
||||||
|
return s == null ? "?" : s;
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fail(String message)
|
||||||
|
{
|
||||||
|
System.err.println("ERROR:" + message);
|
||||||
|
System.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isBlank(String s)
|
||||||
|
{
|
||||||
|
return s == null || s.trim().length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Args
|
||||||
|
{
|
||||||
|
String command;
|
||||||
|
String domain;
|
||||||
|
String url;
|
||||||
|
String user;
|
||||||
|
String password;
|
||||||
|
String container;
|
||||||
|
int timeoutSec = 120;
|
||||||
|
|
||||||
|
static Args parse(String[] args)
|
||||||
|
{
|
||||||
|
Args a = new Args();
|
||||||
|
if (args == null || args.length == 0)
|
||||||
|
{
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.command = args[0].toLowerCase();
|
||||||
|
for (int i = 1; i < args.length; i++)
|
||||||
|
{
|
||||||
|
String key = args[i];
|
||||||
|
if (!key.startsWith("--") || i + 1 >= args.length)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String val = args[++i];
|
||||||
|
if ("--domain".equals(key)) a.domain = val;
|
||||||
|
else if ("--url".equals(key)) a.url = val;
|
||||||
|
else if ("--user".equals(key)) a.user = val;
|
||||||
|
else if ("--password".equals(key)) a.password = val;
|
||||||
|
else if ("--container".equals(key)) a.container = val;
|
||||||
|
else if ("--timeout".equals(key))
|
||||||
|
{
|
||||||
|
try { a.timeoutSec = Integer.parseInt(val); } catch (Exception ignore) { /* keep default */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlank(a.password))
|
||||||
|
{
|
||||||
|
String env = System.getenv("ESB_SONIC_PASSWORD");
|
||||||
|
if (!isBlank(env))
|
||||||
|
{
|
||||||
|
a.password = env;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,9 @@
|
|||||||
<None Update="Sql\**\*">
|
<None Update="Sql\**\*">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Tools\**\*">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -10,18 +10,21 @@
|
|||||||
"Name": "DE-Test",
|
"Name": "DE-Test",
|
||||||
"DomainName": "proalpha-test",
|
"DomainName": "proalpha-test",
|
||||||
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
||||||
// Sonic SMC / Domain Manager (NICHT für WinRM):
|
// Sonic SMC / Domain Manager (für MfApi-Neustart):
|
||||||
"Username": "Administrator",
|
"Username": "Administrator",
|
||||||
"Password": "Administrator",
|
"Password": "Administrator",
|
||||||
// Windows für WinRM: leer = aktueller Benutzer (z.B. gisler). Nie Sonic-SMC-Logins hier eintragen.
|
// Standard: Sonic MF Management API (IAgentProxy.restart) – kein WinRM.
|
||||||
// Lokal auf dem Sonic-Server testen: ManagementMode auf "LocalCmd" setzen (kein WinRM nötig).
|
// Benötigt Java/JDK + Client-JARs unter SonicHome\\lib (oder MfClientLibPath).
|
||||||
"WinRmUsername": "",
|
// Optional-Fallback: "WinRm" / "LocalCmd" (stopcontainer/startcontainer).
|
||||||
"WinRmPassword": "",
|
"ManagementMode": "MfApi",
|
||||||
"ManagementMode": "WinRm",
|
|
||||||
"SonicHome": "C:\\Sonic\\MQ10.0",
|
"SonicHome": "C:\\Sonic\\MQ10.0",
|
||||||
|
"MfClientLibPath": "",
|
||||||
"KnownContainers": [
|
"KnownContainers": [
|
||||||
"DE-Test"
|
"DE-Test"
|
||||||
],
|
],
|
||||||
|
// Nur für ManagementMode=WinRm relevant; leer lassen wenn nicht genutzt:
|
||||||
|
"WinRmUsername": "",
|
||||||
|
"WinRmPassword": "",
|
||||||
"WinRmPort": 5985,
|
"WinRmPort": 5985,
|
||||||
"TimeoutSeconds": 120,
|
"TimeoutSeconds": 120,
|
||||||
"PostRestartDelaySeconds": 20,
|
"PostRestartDelaySeconds": 20,
|
||||||
|
|||||||
Reference in New Issue
Block a user