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>
This commit is contained in:
@@ -40,7 +40,6 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
private TextBox txtCertificatePath = null!;
|
private TextBox txtCertificatePath = null!;
|
||||||
private DataGridView dgvTargets = null!;
|
private DataGridView dgvTargets = null!;
|
||||||
private ComboBox cmbSonicConnection = null!;
|
private ComboBox cmbSonicConnection = null!;
|
||||||
private Button btnLoadFromSonic = null!;
|
|
||||||
private Label lblSonicStatus = null!;
|
private Label lblSonicStatus = null!;
|
||||||
|
|
||||||
private Panel navProgressPanel = null!;
|
private Panel navProgressPanel = null!;
|
||||||
@@ -1302,14 +1301,12 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
if (_loadedTargets.Count == 0)
|
if (_loadedTargets.Count == 0)
|
||||||
{
|
{
|
||||||
SetStatus(
|
SetStatus(
|
||||||
"Keine KnownContainers in appsettings. SonicConnections[].KnownContainers setzen oder „Container laden“.",
|
"Keine KnownContainers in appsettings. SonicConnections[].KnownContainers setzen.",
|
||||||
isError: true);
|
isError: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetStatus(
|
SetStatus($"{_loadedTargets.Count} Container aus appsettings.", isError: false);
|
||||||
$"{_loadedTargets.Count} Container aus appsettings. Optional: „Container laden“ für Live-Liste.",
|
|
||||||
isError: false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1408,10 +1405,6 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
dgvTargets.Enabled = idle;
|
dgvTargets.Enabled = idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (btnLoadFromSonic is not null)
|
|
||||||
{
|
|
||||||
btnLoadFromSonic.Enabled = idle && _sonicDiscovery.HasConnections;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetStatus(string message, bool isError)
|
private void SetStatus(string message, bool isError)
|
||||||
@@ -1555,24 +1548,16 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
SetTargetRowStatus(targetResult.TargetId, targetResult.StatusText);
|
SetTargetRowStatus(targetResult.TargetId, targetResult.StatusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
string details = string.Join(
|
|
||||||
Environment.NewLine + Environment.NewLine,
|
|
||||||
runResult.TargetResults.Select(r =>
|
|
||||||
$"{r.TargetName}: {r.StatusText}" +
|
|
||||||
(string.IsNullOrWhiteSpace(r.Detail) ? string.Empty : Environment.NewLine + r.Detail)));
|
|
||||||
|
|
||||||
SetStatus(
|
SetStatus(
|
||||||
runResult.OverallSuccess
|
runResult.OverallSuccess
|
||||||
? $"Neustart verifiziert ({runResult.TargetResults.Count} Ziel(e))."
|
? $"Neustart verifiziert ({runResult.TargetResults.Count} Ziel(e))."
|
||||||
: "Neustart fehlgeschlagen oder nicht verifiziert.",
|
: "Neustart fehlgeschlagen oder nicht verifiziert.",
|
||||||
isError: !runResult.OverallSuccess);
|
isError: !runResult.OverallSuccess);
|
||||||
|
|
||||||
MessageBox.Show(
|
if (runResult.OverallSuccess)
|
||||||
this,
|
{
|
||||||
details,
|
UpdateToNextStep(4);
|
||||||
runResult.OverallSuccess ? "Neustart verifiziert" : "Neustart fehlgeschlagen",
|
}
|
||||||
MessageBoxButtons.OK,
|
|
||||||
runResult.OverallSuccess ? MessageBoxIcon.Information : MessageBoxIcon.Warning);
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -1693,7 +1678,7 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// Sonic Management Console – Container-Discovery
|
// Sonic – Status (KnownContainers aus appsettings)
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
private void BuildSonicDiscoveryRow(Panel card)
|
private void BuildSonicDiscoveryRow(Panel card)
|
||||||
@@ -1723,19 +1708,13 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
cmbSonicConnection.Items.Add(conn.Name);
|
cmbSonicConnection.Items.Add(conn.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
btnLoadFromSonic = CreateSecondaryButton("Container laden");
|
|
||||||
btnLoadFromSonic.Location = new Point(392, 103);
|
|
||||||
btnLoadFromSonic.Size = new Size(148, 28);
|
|
||||||
btnLoadFromSonic.Enabled = _sonicDiscovery.HasConnections;
|
|
||||||
btnLoadFromSonic.Click += async (_, _) => await LoadFromSonicAsync();
|
|
||||||
|
|
||||||
lblSonicStatus = new Label
|
lblSonicStatus = new Label
|
||||||
{
|
{
|
||||||
ForeColor = MutedTextColor,
|
ForeColor = MutedTextColor,
|
||||||
Font = new Font("Segoe UI", 8.5f),
|
Font = new Font("Segoe UI", 8.5f),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
MaximumSize = new Size(720, 0),
|
MaximumSize = new Size(720, 0),
|
||||||
Location = new Point(552, 100)
|
Location = new Point(392, 108)
|
||||||
};
|
};
|
||||||
|
|
||||||
cmbSonicConnection.SelectedIndexChanged += (_, _) => RefreshSonicStatusLine();
|
cmbSonicConnection.SelectedIndexChanged += (_, _) => RefreshSonicStatusLine();
|
||||||
@@ -1751,11 +1730,10 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
|
|
||||||
card.Controls.Add(sonicLabel);
|
card.Controls.Add(sonicLabel);
|
||||||
card.Controls.Add(cmbSonicConnection);
|
card.Controls.Add(cmbSonicConnection);
|
||||||
card.Controls.Add(btnLoadFromSonic);
|
|
||||||
card.Controls.Add(lblSonicStatus);
|
card.Controls.Add(lblSonicStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshSonicStatusLine(string? suffix = null)
|
private void RefreshSonicStatusLine()
|
||||||
{
|
{
|
||||||
if (!_sonicDiscovery.HasConnections)
|
if (!_sonicDiscovery.HasConnections)
|
||||||
{
|
{
|
||||||
@@ -1769,134 +1747,13 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
.FirstOrDefault(c => string.Equals(c.Name, selectedName, StringComparison.OrdinalIgnoreCase))
|
.FirstOrDefault(c => string.Equals(c.Name, selectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
?? _sonicDiscovery.Connections[0];
|
?? _sonicDiscovery.Connections[0];
|
||||||
|
|
||||||
string sonicHome = string.IsNullOrWhiteSpace(conn.SonicHome) ? "(leer)" : conn.SonicHome.Trim();
|
|
||||||
(string home, string? javaExe) = new SonicMfApiExecutor(conn).ResolveRuntimePaths();
|
(string home, string? javaExe) = new SonicMfApiExecutor(conn).ResolveRuntimePaths();
|
||||||
string javaDisplay = string.IsNullOrWhiteSpace(javaExe)
|
string javaDisplay = string.IsNullOrWhiteSpace(javaExe)
|
||||||
? "java=? (unter SonicHome/JRE setzen)"
|
? "java=? (JavaPath in appsettings setzen)"
|
||||||
: $"java={javaExe}";
|
: $"java={javaExe}";
|
||||||
string baseText =
|
lblSonicStatus.Text = $"{conn.ManagementModeDisplay} | SonicHome={home} | {javaDisplay}";
|
||||||
$"{conn.ManagementModeDisplay} | SonicHome={home} | {javaDisplay}";
|
|
||||||
if (!string.IsNullOrWhiteSpace(suffix))
|
|
||||||
{
|
|
||||||
baseText = $"{suffix} | {baseText}";
|
|
||||||
}
|
|
||||||
|
|
||||||
lblSonicStatus.Text = baseText;
|
|
||||||
lblSonicStatus.ForeColor = string.IsNullOrWhiteSpace(javaExe) ? GoldColor : GreenColor;
|
lblSonicStatus.ForeColor = string.IsNullOrWhiteSpace(javaExe) ? GoldColor : GreenColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string TruncateStatus(string? text, int max = 120)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
|
||||||
{
|
|
||||||
return "unbekannt";
|
|
||||||
}
|
|
||||||
|
|
||||||
string oneLine = text.Replace('\r', ' ').Replace('\n', ' ').Trim();
|
|
||||||
return oneLine.Length <= max ? oneLine : oneLine[..max] + "…";
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadFromSonicAsync()
|
|
||||||
{
|
|
||||||
if (_isOperationRunning) return;
|
|
||||||
|
|
||||||
string? selectedConnection = cmbSonicConnection?.SelectedItem?.ToString();
|
|
||||||
if (string.IsNullOrWhiteSpace(selectedConnection)) return;
|
|
||||||
|
|
||||||
btnLoadFromSonic.Enabled = false;
|
|
||||||
lblSonicStatus.Text = $"Verbinde mit {selectedConnection}…";
|
|
||||||
lblSonicStatus.ForeColor = GoldColor;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(30));
|
|
||||||
SonicDiscoveryResult result = await _sonicDiscovery.DiscoverAsync(
|
|
||||||
selectedConnection,
|
|
||||||
_loadedTargets,
|
|
||||||
cts.Token);
|
|
||||||
|
|
||||||
if (!result.Success)
|
|
||||||
{
|
|
||||||
RefreshSonicStatusLine($"Fehler: {TruncateStatus(result.ErrorMessage)}");
|
|
||||||
lblSonicStatus.ForeColor = RedColor;
|
|
||||||
SetStatus($"Sonic-Discovery fehlgeschlagen: {result.ErrorMessage}", isError: true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grid mit entdeckten + konfigurierten Containern befüllen
|
|
||||||
_loadedTargets = result.DiscoveredTargets;
|
|
||||||
BindTargetsToGrid(result.DiscoveredTargets);
|
|
||||||
|
|
||||||
int total = result.DiscoveredTargets.Count;
|
|
||||||
int remote = result.RawContainerNames.Count;
|
|
||||||
string domain = string.IsNullOrWhiteSpace(result.DomainName)
|
|
||||||
? selectedConnection
|
|
||||||
: result.DomainName;
|
|
||||||
|
|
||||||
if (total == 0)
|
|
||||||
{
|
|
||||||
RefreshSonicStatusLine($"Verb. ok – 0 Container '{domain}'");
|
|
||||||
lblSonicStatus.ForeColor = GoldColor;
|
|
||||||
SetStatus(
|
|
||||||
result.ErrorMessage
|
|
||||||
?? $"Sonic Domain '{domain}': keine Container. KnownContainers/SonicHome in appsettings prüfen.",
|
|
||||||
isError: true);
|
|
||||||
}
|
|
||||||
else if (remote == 0)
|
|
||||||
{
|
|
||||||
RefreshSonicStatusLine($"Verb. ok – 0 remote, {total} Config '{domain}'");
|
|
||||||
lblSonicStatus.ForeColor = GoldColor;
|
|
||||||
SetStatus(
|
|
||||||
$"Domain '{domain}': Remote leer – {total} Ziel(e) aus appsettings/KnownContainers. "
|
|
||||||
+ (result.ErrorMessage ?? string.Empty),
|
|
||||||
isError: false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RefreshSonicStatusLine($"Domain '{domain}': {remote} remote, {total} gesamt");
|
|
||||||
SetStatus($"Sonic Domain '{domain}': {total} Ziel(e) geladen ({remote} remote).", isError: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkUnconfiguredRows();
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
RefreshSonicStatusLine("Timeout");
|
|
||||||
lblSonicStatus.ForeColor = RedColor;
|
|
||||||
SetStatus($"Sonic-Discovery Timeout ({selectedConnection})", isError: true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
RefreshSonicStatusLine($"Fehler: {TruncateStatus(ex.Message)}");
|
|
||||||
lblSonicStatus.ForeColor = RedColor;
|
|
||||||
SetStatus($"Sonic-Discovery Fehler: {ex.Message}", isError: true);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
btnLoadFromSonic.Enabled = _sonicDiscovery.HasConnections && !_isOperationRunning;
|
|
||||||
RefreshActionButtonStates();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Färbt Zeilen mit unvollständiger Konfiguration (kein TargetDirectory) orange ein
|
|
||||||
/// damit erkennbar ist, dass diese Container noch konfiguriert werden müssen.
|
|
||||||
/// </summary>
|
|
||||||
private void MarkUnconfiguredRows()
|
|
||||||
{
|
|
||||||
foreach (DataGridViewRow row in dgvTargets.Rows)
|
|
||||||
{
|
|
||||||
if (row.Tag is not DeploymentTarget target) continue;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(target.TargetDirectory))
|
|
||||||
{
|
|
||||||
row.DefaultCellStyle.ForeColor = GoldColor;
|
|
||||||
if (row.Cells["Status"] is DataGridViewCell statusCell)
|
|
||||||
{
|
|
||||||
statusCell.Value = "⚠ Pfad fehlt";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,390 +1,39 @@
|
|||||||
namespace ZA.CoreService.ESBCertificateManager.Models;
|
namespace ZA.CoreService.ESBCertificateManager.Models;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verbindungskonfiguration für eine Progress Sonic ESB Management Instanz.
|
/// Sonic-Verbindung für MfApi-Container-Restart (wie SMC).
|
||||||
/// Name = Verbindungs-Alias (hier oft gleich ContainerName, z.B. "DE-Test").
|
|
||||||
/// DomainName = Sonic-Domain (z.B. "proalpha-test").
|
|
||||||
/// Container wird in KnownContainers / DeploymentTarget.ContainerName geführt (z.B. "DE-Test").
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SonicConnection
|
public sealed class SonicConnection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>Alias; referenziert von DeploymentTarget.SonicConnectionName.</summary>
|
||||||
/// Alias der Verbindung; referenziert von <see cref="DeploymentTarget.SonicConnectionName"/>.
|
|
||||||
/// </summary>
|
|
||||||
public required string Name { get; init; }
|
public required string Name { get; init; }
|
||||||
|
|
||||||
/// <summary>Sonic-Domain, z.B. "proalpha-test" (nicht der Containername).</summary>
|
/// <summary>Sonic-Domain, z.B. proalpha-test.</summary>
|
||||||
public required string DomainName { get; init; }
|
public required string DomainName { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Broker-/Management-URL wie in der SMC, z.B. tcp://host:13070.</summary>
|
||||||
/// Dieselbe Broker-/Management-URL wie in der Sonic Management Console (SMC),
|
|
||||||
/// z.B. "tcp://dekun-painwbdet:13070". Keine separate Domain-Console nötig.
|
|
||||||
/// </summary>
|
|
||||||
public required string ConnectionUrl { get; init; }
|
public required string ConnectionUrl { get; init; }
|
||||||
|
|
||||||
/// <summary>Login wie in der Sonic Management Console (nicht für WinRM).</summary>
|
|
||||||
public required string Username { get; init; }
|
public required string Username { get; init; }
|
||||||
|
|
||||||
/// <summary>Passwort wie in der Sonic Management Console (nicht für WinRM).</summary>
|
|
||||||
public required string Password { get; init; }
|
public required string Password { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Installationsroot (optional; Libs oft unter lib).</summary>
|
||||||
/// Windows-Konto für WinRM (<c>Invoke-Command -Credential</c>).
|
|
||||||
/// Leer = aktueller Prozess-Benutzer ohne explizite Credentials.
|
|
||||||
/// Nicht mit <see cref="Username"/>/<see cref="Password"/> (Sonic SMC) verwechseln.
|
|
||||||
/// </summary>
|
|
||||||
public string WinRmUsername { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>Windows-Passwort für WinRM; nur relevant wenn <see cref="WinRmUsername"/> gesetzt ist.</summary>
|
|
||||||
public string WinRmPassword { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// MfApi = Sonic MF Management Runtime API über ConnectionUrl + SMC-Logins (Standard);
|
|
||||||
/// WinRm / LocalCmd = optionaler Fallback über stopcontainer/startcontainer;
|
|
||||||
/// HttpApi = REST (falls vorhanden).
|
|
||||||
/// </summary>
|
|
||||||
/// <summary>
|
|
||||||
/// Standard: MfApi = Management Application API (wie SMC):
|
|
||||||
/// JMSConnectorClient + MFProxyFactory.createAgentProxy + IAgentProxy.restart.
|
|
||||||
/// Laut CX-Messenger-Doku-Index: Docs2017/api/mgmt_api (nicht stopcontainer.bat).
|
|
||||||
/// LocalCmd nur wenn Server-Scripts existieren (bei reiner SMC oft nicht der Fall).
|
|
||||||
/// </summary>
|
|
||||||
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.MfApi;
|
|
||||||
|
|
||||||
/// <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.</summary>
|
|
||||||
public string ManagementModeDisplay => "MfApi (SMC Management Application API)";
|
|
||||||
|
|
||||||
/// <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-/MQ-/ESB-Installationsroot, z.B. "C:\Sonic\MQ10.0" (Client-JARs oft unter lib).
|
|
||||||
/// Bei ESB-Home: ggf. MfClientLibPath auf MQ\lib setzen.
|
|
||||||
/// </summary>
|
|
||||||
public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0";
|
public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Optional: JRE/JDK-Home.</summary>
|
||||||
/// Optional: JRE/JDK-Home (z.B. C:\Sonic\MQ10.0\jre) oder Pfad zu java.exe.
|
|
||||||
/// Leer = automatische Suche (JAVA_HOME/JRE_HOME, setenv.bat, rekursiv unter SonicHome, Registry, …).
|
|
||||||
/// </summary>
|
|
||||||
public string JavaHome { get; init; } = string.Empty;
|
public string JavaHome { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Optional: direkter Pfad zu java.exe.</summary>
|
||||||
/// Optionaler direkter Pfad zu java.exe (überschreibt JavaHome wenn gesetzt).
|
|
||||||
/// </summary>
|
|
||||||
public string JavaPath { get; init; } = string.Empty;
|
public string JavaPath { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Ordner mit mgmt_client.jar / mfcontext.jar / …</summary>
|
||||||
/// Optionaler Pfad zu Sonic-Client-JARs für MfApi (mgmt_client.jar etc.).
|
|
||||||
/// Leer = SonicHome\lib, MQ_HOME\lib, ESB_HOME\lib bzw. nested MQ*/lib.
|
|
||||||
/// </summary>
|
|
||||||
public string MfClientLibPath { get; init; } = string.Empty;
|
public string MfClientLibPath { get; init; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Container-Kurznamen, z.B. ct-ZADBService.</summary>
|
||||||
/// Container-Namen (kurz), z.B. ["DE-Test"]. Nicht die Domain.
|
|
||||||
/// </summary>
|
|
||||||
public List<string> KnownContainers { get; init; } = [];
|
public List<string> KnownContainers { get; init; } = [];
|
||||||
|
|
||||||
public int ManagementHttpPort { get; init; } = 8080;
|
|
||||||
public string ApiBasePath { get; init; } = "/api/v1";
|
|
||||||
public string ContainerListPath { get; init; } = string.Empty;
|
|
||||||
public string ContainerRestartPath { get; init; } = string.Empty;
|
|
||||||
public string ContainerStopPath { get; init; } = string.Empty;
|
|
||||||
public string ContainerStartPath { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
public int WinRmPort { get; init; } = 5985;
|
|
||||||
public string WinRmRestartScript { get; init; } = string.Empty;
|
|
||||||
public string WinRmContainerListScript { get; init; } = string.Empty;
|
|
||||||
public string WinRmXapiImportScript { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
public int TimeoutSeconds { get; init; } = 120;
|
public int TimeoutSeconds { get; init; } = 120;
|
||||||
public int PostRestartDelaySeconds { get; init; } = 20;
|
public int PostRestartDelaySeconds { get; init; } = 20;
|
||||||
|
|
||||||
public string ResolveRestartScript()
|
public string ManagementModeDisplay => "MfApi (SMC Restart)";
|
||||||
=> string.IsNullOrWhiteSpace(WinRmRestartScript)
|
|
||||||
? DefaultRestartScript
|
|
||||||
: WinRmRestartScript;
|
|
||||||
|
|
||||||
public string ResolveContainerListScript()
|
|
||||||
=> string.IsNullOrWhiteSpace(WinRmContainerListScript)
|
|
||||||
? DefaultContainerListScript
|
|
||||||
: WinRmContainerListScript;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Offizieller MF-Container-Neustart laut Aurea CX Messenger Doku:
|
|
||||||
/// SonicHome\bin\stopcontainer.bat Domain.Container
|
|
||||||
/// SonicHome\bin\startcontainer.bat Domain.Container
|
|
||||||
/// Danach Prozess-Verifikation (alte PIDs weg, neue PIDs da).
|
|
||||||
/// </summary>
|
|
||||||
public const string DefaultRestartScript =
|
|
||||||
"""
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
$sonicHome = '{sonicHome}'
|
|
||||||
$domain = '{domain}'
|
|
||||||
$container = '{container}'
|
|
||||||
|
|
||||||
$bin = Join-Path $sonicHome 'bin'
|
|
||||||
$stopBat = Join-Path $bin 'stopcontainer.bat'
|
|
||||||
$startBat = Join-Path $bin 'startcontainer.bat'
|
|
||||||
|
|
||||||
# Kurzname = Container (z.B. DE-Test), Full = Domain.Container (z.B. proalpha-test.DE-Test)
|
|
||||||
$shortName = if ($container -like '*.*') { ($container -split '\.', 2)[1] } else { $container }
|
|
||||||
$fullName = if ($container -like '*.*') { $container } else { "$domain.$container" }
|
|
||||||
$markers = @($shortName, $fullName, "$domain.$shortName") | Select-Object -Unique
|
|
||||||
|
|
||||||
function Get-ContainerPids {
|
|
||||||
$pids = @()
|
|
||||||
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
||||||
Where-Object {
|
|
||||||
$cmd = $_.CommandLine
|
|
||||||
if (-not $cmd) { return $false }
|
|
||||||
if ($_.Name -notmatch 'java|javaw|sonic') { return $false }
|
|
||||||
foreach ($m in $markers) {
|
|
||||||
if ($cmd -like "*$m*") { return $true }
|
|
||||||
}
|
|
||||||
return $false
|
|
||||||
} |
|
|
||||||
ForEach-Object { $pids += [int]$_.ProcessId }
|
|
||||||
return @($pids | Select-Object -Unique)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Wait-PidsGone([int[]]$pids, [int]$seconds) {
|
|
||||||
$deadline = (Get-Date).AddSeconds($seconds)
|
|
||||||
while ((Get-Date) -lt $deadline) {
|
|
||||||
$alive = @($pids | Where-Object { Get-Process -Id $_ -ErrorAction SilentlyContinue })
|
|
||||||
if ($alive.Count -eq 0) { return $true }
|
|
||||||
Start-Sleep -Seconds 2
|
|
||||||
}
|
|
||||||
return $false
|
|
||||||
}
|
|
||||||
|
|
||||||
function Wait-NewPids([int[]]$oldPids, [int]$seconds) {
|
|
||||||
$deadline = (Get-Date).AddSeconds($seconds)
|
|
||||||
while ((Get-Date) -lt $deadline) {
|
|
||||||
$now = @(Get-ContainerPids)
|
|
||||||
$fresh = @($now | Where-Object { $oldPids -notcontains $_ })
|
|
||||||
if ($fresh.Count -gt 0) { return ,$fresh }
|
|
||||||
Start-Sleep -Seconds 2
|
|
||||||
}
|
|
||||||
return ,@()
|
|
||||||
}
|
|
||||||
|
|
||||||
function Invoke-ContainerBat([string]$bat, [string]$name) {
|
|
||||||
if (-not (Test-Path -LiteralPath $bat)) {
|
|
||||||
throw "Sonic BAT fehlt: $bat (SonicHome pruefen)"
|
|
||||||
}
|
|
||||||
$arg = '/c "' + $bat + '" "' + $name + '"'
|
|
||||||
$p = Start-Process -FilePath 'cmd.exe' -ArgumentList $arg -Wait -PassThru -NoNewWindow
|
|
||||||
return [int]$p.ExitCode
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Output "INFO:Domain=$domain Container=$shortName Full=$fullName"
|
|
||||||
Write-Output "INFO:RestartVia=stopcontainer/startcontainer (MF Container)"
|
|
||||||
$before = @(Get-ContainerPids)
|
|
||||||
Write-Output "INFO:PIDsBefore=$($before -join ',')"
|
|
||||||
|
|
||||||
if ($before.Count -eq 0) {
|
|
||||||
Write-Output "WARN:No running Java/Sonic process for '$shortName' - starting anyway"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 1) Offiziell: stopcontainer.bat Domain.Container (danach Kurzname als Fallback)
|
|
||||||
$stopOk = $false
|
|
||||||
foreach ($n in @($fullName, $shortName)) {
|
|
||||||
Write-Output "INFO:stopcontainer $n"
|
|
||||||
$code = Invoke-ContainerBat -bat $stopBat -name $n
|
|
||||||
Write-Output "INFO:stopcontainer ExitCode=$code Name=$n"
|
|
||||||
if ($code -eq 0) { $stopOk = $true; break }
|
|
||||||
}
|
|
||||||
if (-not $stopOk) {
|
|
||||||
Write-Output "WARN:stopcontainer non-zero; will force-stop remaining PIDs if any"
|
|
||||||
}
|
|
||||||
|
|
||||||
Start-Sleep -Seconds 3
|
|
||||||
$still = @(Get-ContainerPids)
|
|
||||||
|
|
||||||
# 2) Force-Stop falls BAT den Prozess nicht beendet (sonst kein echter Restart in SMC)
|
|
||||||
if ($still.Count -gt 0) {
|
|
||||||
Write-Output "INFO:Force-Stop PIDs=$($still -join ',')"
|
|
||||||
foreach ($procId in $still) {
|
|
||||||
Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($before.Count -gt 0) {
|
|
||||||
if (-not (Wait-PidsGone -pids $before -seconds 45)) {
|
|
||||||
throw "Container process still running after stop (PIDs=$($before -join ','))"
|
|
||||||
}
|
|
||||||
Write-Output "INFO:Old PIDs gone"
|
|
||||||
}
|
|
||||||
|
|
||||||
Start-Sleep -Seconds 3
|
|
||||||
|
|
||||||
# 3) Offiziell: startcontainer.bat Domain.Container
|
|
||||||
$started = $false
|
|
||||||
foreach ($n in @($fullName, $shortName)) {
|
|
||||||
Write-Output "INFO:startcontainer $n"
|
|
||||||
$code = Invoke-ContainerBat -bat $startBat -name $n
|
|
||||||
Write-Output "INFO:startcontainer ExitCode=$code Name=$n"
|
|
||||||
if ($code -eq 0) { $started = $true; break }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $started) {
|
|
||||||
throw "startcontainer failed for $fullName / $shortName"
|
|
||||||
}
|
|
||||||
|
|
||||||
$after = @(Wait-NewPids -oldPids $before -seconds 60)
|
|
||||||
if ($after.Count -eq 0) {
|
|
||||||
$any = @(Get-ContainerPids)
|
|
||||||
if ($any.Count -eq 0) {
|
|
||||||
throw "After start no process for container '$shortName'. Check SMC / SonicHome / ContainerName."
|
|
||||||
}
|
|
||||||
Write-Output "INFO:PIDsAfter=$($any -join ',')"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Output "INFO:PIDsAfter=$($after -join ',')"
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName"
|
|
||||||
""";
|
|
||||||
|
|
||||||
public const string DefaultContainerListScript =
|
|
||||||
"""
|
|
||||||
$ErrorActionPreference = 'Continue'
|
|
||||||
$sonicHome = '{sonicHome}'
|
|
||||||
$domain = '{domain}'
|
|
||||||
$names = New-Object System.Collections.Generic.List[string]
|
|
||||||
|
|
||||||
function Add-Name([string]$n) {
|
|
||||||
if ([string]::IsNullOrWhiteSpace($n)) { return }
|
|
||||||
$n = $n.Trim()
|
|
||||||
if (-not $names.Contains($n)) { [void]$names.Add($n) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path -LiteralPath $sonicHome)) {
|
|
||||||
Write-Output "WARN:SonicHomeNichtGefunden:$sonicHome"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Output "INFO:SonicHomeOk:$sonicHome Domain=$domain"
|
|
||||||
|
|
||||||
Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue |
|
|
||||||
ForEach-Object {
|
|
||||||
if ($_.Name -like '*.cache') {
|
|
||||||
Add-Name ($_.Name -replace '\.cache$', '')
|
|
||||||
}
|
|
||||||
Get-ChildItem -LiteralPath $_.FullName -Directory -ErrorAction SilentlyContinue |
|
|
||||||
Where-Object { $_.Name -like '*.cache' } |
|
|
||||||
ForEach-Object { Add-Name ($_.Name -replace '\.cache$', '') }
|
|
||||||
}
|
|
||||||
|
|
||||||
Get-ChildItem -LiteralPath $sonicHome -Recurse -Filter 'container.xml' -File -ErrorAction SilentlyContinue |
|
|
||||||
Select-Object -First 40 |
|
|
||||||
ForEach-Object {
|
|
||||||
Add-Name (Split-Path $_.DirectoryName -Leaf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
||||||
Where-Object { $_.CommandLine -match 'sonic|mf\.framework|container\.xml' } |
|
|
||||||
ForEach-Object {
|
|
||||||
if ($_.CommandLine -match '([A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)\.cache') {
|
|
||||||
Add-Name $Matches[1]
|
|
||||||
}
|
|
||||||
elseif ($_.CommandLine -match '\\([A-Za-z0-9_\-]+)\\container\.xml') {
|
|
||||||
Add-Name $Matches[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Kurzform Domain.Container → Container
|
|
||||||
foreach ($n in @($names.ToArray())) {
|
|
||||||
if ($n -like '*.*') {
|
|
||||||
$parts = $n -split '\.', 2
|
|
||||||
if ($parts.Count -eq 2) { Add-Name $parts[1] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($names.Count -eq 0) {
|
|
||||||
Write-Output "WARN:KeineContainerGefunden Domain=$domain SonicHome=$sonicHome"
|
|
||||||
}
|
|
||||||
|
|
||||||
$names | Sort-Object -Unique
|
|
||||||
""";
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SonicManagementMode
|
|
||||||
{
|
|
||||||
HttpApi = 0,
|
|
||||||
WinRm = 1,
|
|
||||||
LocalCmd = 2,
|
|
||||||
/// <summary>Sonic MF Management API über Domain-Manager (ConnectionUrl + SMC-Credentials).</summary>
|
|
||||||
MfApi = 3
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ using ZA.CoreService.ESBCertificateManager.Models;
|
|||||||
namespace ZA.CoreService.ESBCertificateManager.Services;
|
namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fragt konfigurierte Sonic-Verbindungen nach Containern ab
|
/// Liefert Ziele nur aus appsettings KnownContainers (kein Live-Ping/List).
|
||||||
/// und merged Remote-Treffer mit KnownContainers / Sample-Zielen.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SonicContainerDiscovery
|
public sealed class SonicContainerDiscovery
|
||||||
{
|
{
|
||||||
@@ -18,9 +17,6 @@ public sealed class SonicContainerDiscovery
|
|||||||
public bool HasConnections => _connections.Count > 0;
|
public bool HasConnections => _connections.Count > 0;
|
||||||
public IReadOnlyList<SonicConnection> Connections => _connections;
|
public IReadOnlyList<SonicConnection> Connections => _connections;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ziele nur aus appsettings KnownContainers (kein Offline-JSON).
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<DeploymentTarget> BuildTargetsFromConfig()
|
public IReadOnlyList<DeploymentTarget> BuildTargetsFromConfig()
|
||||||
{
|
{
|
||||||
List<DeploymentTarget> result = [];
|
List<DeploymentTarget> result = [];
|
||||||
@@ -49,252 +45,4 @@ public sealed class SonicContainerDiscovery
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SonicDiscoveryResult> DiscoverAsync(
|
|
||||||
string connectionName,
|
|
||||||
IReadOnlyList<DeploymentTarget> knownTargets,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
SonicConnection? connection = _connections
|
|
||||||
.FirstOrDefault(c => string.Equals(c.Name, connectionName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (connection is null)
|
|
||||||
{
|
|
||||||
return SonicDiscoveryResult.Failed(connectionName,
|
|
||||||
$"Sonic-Verbindung '{connectionName}' ist nicht in AppSettings konfiguriert.");
|
|
||||||
}
|
|
||||||
|
|
||||||
using SonicManagementClient client = new(connection);
|
|
||||||
|
|
||||||
(bool reachable, string? pingError, string? resolvedPath) = await client.CheckConnectionAsync(cancellationToken);
|
|
||||||
|
|
||||||
List<string> diagnostics = [];
|
|
||||||
List<string> remoteContainers = [];
|
|
||||||
bool listOk = false;
|
|
||||||
string? listError = null;
|
|
||||||
|
|
||||||
if (!reachable)
|
|
||||||
{
|
|
||||||
// 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)))
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!listOk)
|
|
||||||
{
|
|
||||||
diagnostics.Add($"Listen-Fehler: {listError}");
|
|
||||||
remoteContainers = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: explizit in appsettings hinterlegte Container
|
|
||||||
List<string> mergedNames = MergeContainerNames(
|
|
||||||
remoteContainers,
|
|
||||||
connection.KnownContainers,
|
|
||||||
knownTargets
|
|
||||||
.Where(t => string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
.Select(t => t.ContainerName)
|
|
||||||
.Where(n => !string.IsNullOrWhiteSpace(n)));
|
|
||||||
|
|
||||||
List<DeploymentTarget> discovered = BuildTargets(connection, mergedNames, knownTargets);
|
|
||||||
|
|
||||||
string? hint = BuildHint(connection, remoteContainers.Count, connection.KnownContainers.Count, diagnostics, listOk, listError);
|
|
||||||
|
|
||||||
return new SonicDiscoveryResult
|
|
||||||
{
|
|
||||||
ConnectionName = connectionName,
|
|
||||||
DomainName = connection.DomainName,
|
|
||||||
Success = true,
|
|
||||||
ErrorMessage = hint,
|
|
||||||
DiscoveredTargets = discovered,
|
|
||||||
RawContainerNames = remoteContainers,
|
|
||||||
ResolvedPath = resolvedPath
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<string> MergeContainerNames(
|
|
||||||
IEnumerable<string> remote,
|
|
||||||
IEnumerable<string> knownConfigured,
|
|
||||||
IEnumerable<string> knownFromTargets)
|
|
||||||
{
|
|
||||||
List<string> result = [];
|
|
||||||
|
|
||||||
foreach (string name in remote.Concat(knownConfigured).Concat(knownFromTargets))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.Any(existing => ContainerNamesMatch(existing, name, domain: null)))
|
|
||||||
{
|
|
||||||
result.Add(name.Trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? BuildHint(
|
|
||||||
SonicConnection connection,
|
|
||||||
int remoteCount,
|
|
||||||
int knownConfigCount,
|
|
||||||
List<string> diagnostics,
|
|
||||||
bool listOk,
|
|
||||||
string? listError)
|
|
||||||
{
|
|
||||||
List<string> parts = [];
|
|
||||||
|
|
||||||
if (!listOk && !string.IsNullOrWhiteSpace(listError))
|
|
||||||
{
|
|
||||||
parts.Add(listError!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remoteCount == 0)
|
|
||||||
{
|
|
||||||
parts.Add(
|
|
||||||
$"Remote hat 0 Container geliefert (Domain '{connection.DomainName}', SonicHome='{connection.SonicHome}').");
|
|
||||||
|
|
||||||
if (knownConfigCount > 0)
|
|
||||||
{
|
|
||||||
parts.Add($"Fallback: {knownConfigCount} KnownContainers aus appsettings.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
parts.Add("Tipp: KnownContainers in appsettings setzen oder SonicHome korrigieren.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string d in diagnostics.Take(3))
|
|
||||||
{
|
|
||||||
parts.Add(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.Count == 0 ? null : string.Join(" ", parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<DeploymentTarget> BuildTargets(
|
|
||||||
SonicConnection connection,
|
|
||||||
IReadOnlyList<string> containerNames,
|
|
||||||
IReadOnlyList<DeploymentTarget> knownTargets)
|
|
||||||
{
|
|
||||||
List<DeploymentTarget> result = [];
|
|
||||||
int syntheticId = -1;
|
|
||||||
|
|
||||||
foreach (string containerName in containerNames)
|
|
||||||
{
|
|
||||||
DeploymentTarget? existing = knownTargets.FirstOrDefault(t =>
|
|
||||||
string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& ContainerNamesMatch(t.ContainerName, containerName, connection.DomainName));
|
|
||||||
|
|
||||||
if (existing is not null)
|
|
||||||
{
|
|
||||||
if (!result.Any(r => r.Id == existing.Id))
|
|
||||||
{
|
|
||||||
result.Add(existing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.Add(new DeploymentTarget
|
|
||||||
{
|
|
||||||
Id = syntheticId--,
|
|
||||||
Name = $"{connection.Name} / {containerName}",
|
|
||||||
Environment = connection.DomainName,
|
|
||||||
IsActive = true,
|
|
||||||
TargetDirectory = string.Empty,
|
|
||||||
CertificateFileName = string.Empty,
|
|
||||||
ContainerName = containerName,
|
|
||||||
RestartType = RestartType.SonicContainer,
|
|
||||||
SonicConnectionName = connection.Name,
|
|
||||||
SortOrder = result.Count * 10
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DeploymentTarget known in knownTargets)
|
|
||||||
{
|
|
||||||
if (!string.Equals(known.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool alreadyAdded = result.Any(r =>
|
|
||||||
ContainerNamesMatch(r.ContainerName, known.ContainerName, connection.DomainName)
|
|
||||||
|| r.Id == known.Id);
|
|
||||||
|
|
||||||
if (!alreadyAdded)
|
|
||||||
{
|
|
||||||
result.Add(known);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool ContainerNamesMatch(string? a, string? b, string? domain)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
string aShort = StripDomainPrefix(a, domain);
|
|
||||||
string bShort = StripDomainPrefix(b, domain);
|
|
||||||
return string.Equals(aShort, bShort, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string StripDomainPrefix(string name, string? domain)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(domain)
|
|
||||||
&& name.StartsWith(domain + ".", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return name[(domain.Length + 1)..];
|
|
||||||
}
|
|
||||||
|
|
||||||
int dot = name.IndexOf('.');
|
|
||||||
return dot > 0 ? name[(dot + 1)..] : name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class SonicDiscoveryResult
|
|
||||||
{
|
|
||||||
public required string ConnectionName { get; init; }
|
|
||||||
public string DomainName { get; init; } = string.Empty;
|
|
||||||
public bool Success { get; init; }
|
|
||||||
public string? ErrorMessage { get; init; }
|
|
||||||
public string? ResolvedPath { get; init; }
|
|
||||||
public IReadOnlyList<DeploymentTarget> DiscoveredTargets { get; init; } = [];
|
|
||||||
public IReadOnlyList<string> RawContainerNames { get; init; } = [];
|
|
||||||
|
|
||||||
public static SonicDiscoveryResult Failed(string connectionName, string error)
|
|
||||||
=> new() { ConnectionName = connectionName, Success = false, ErrorMessage = error };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using ZA.CoreService.ESBCertificateManager.Models;
|
|||||||
namespace ZA.CoreService.ESBCertificateManager.Services;
|
namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sonic MfApi (wie SMC-Neustart) – ohne WinRM/HTTP/XApi.
|
/// Schlanke Fassade: nur Container-Neustart über MfApi.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SonicManagementClient : IDisposable
|
public sealed class SonicManagementClient : IDisposable
|
||||||
{
|
{
|
||||||
@@ -16,45 +16,6 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
_mfApi = new SonicMfApiExecutor(connection);
|
_mfApi = new SonicMfApiExecutor(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool Success, string? Error, string? ResolvedPath)> CheckConnectionAsync(
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
(bool ok, string? error) = await _mfApi.TestConnectionAsync(cancellationToken);
|
|
||||||
if (ok)
|
|
||||||
{
|
|
||||||
return (true, null, $"MfApi {_connection.ConnectionUrl} Domain={_connection.DomainName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_connection.KnownContainers.Count > 0
|
|
||||||
&& await IsTcpReachableAsync(_connection.ConnectionUrl, cancellationToken))
|
|
||||||
{
|
|
||||||
return (true, null,
|
|
||||||
$"MfApi-Ping fehlgeschlagen, KnownContainers/TCP ok. Hinweis: {Truncate(error)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (false, error ?? "MfApi-Verbindung fehlgeschlagen", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(bool Success, IReadOnlyList<string> ContainerNames, string? Error)> GetContainersAsync(
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
(bool ok, IReadOnlyList<string> names, string? error) =
|
|
||||||
await _mfApi.ListContainersAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (ok && names.Count > 0)
|
|
||||||
{
|
|
||||||
return (true, names, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_connection.KnownContainers.Count > 0)
|
|
||||||
{
|
|
||||||
return (true, _connection.KnownContainers,
|
|
||||||
ok ? null : $"Liste leer/fehlerhaft – KnownContainers. {error}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (false, [], error ?? "Keine Container gefunden.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(bool Success, string Status, string? Detail)> RestartContainerAsync(
|
public async Task<(bool Success, string Status, string? Detail)> RestartContainerAsync(
|
||||||
string containerName,
|
string containerName,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
@@ -77,27 +38,6 @@ public sealed class SonicManagementClient : IDisposable
|
|||||||
$"Fehler: {error}\n\n{output}");
|
$"Fehler: {error}\n\n{output}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<bool> IsTcpReachableAsync(string connectionUrl, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Uri uri = new(connectionUrl);
|
|
||||||
using System.Net.Sockets.TcpClient tcp = new();
|
|
||||||
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
||||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
|
||||||
await tcp.ConnectAsync(uri.Host, uri.Port > 0 ? uri.Port : 2506, cts.Token);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string Truncate(string? s, int max = 180)
|
|
||||||
=> string.IsNullOrWhiteSpace(s) ? string.Empty
|
|
||||||
: s.Length <= max ? s : s[..max] + "…";
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -1,27 +1,15 @@
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.management.MBeanOperationInfo;
|
|
||||||
import javax.management.MBeanInfo;
|
|
||||||
import javax.management.ObjectName;
|
import javax.management.ObjectName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sonic Management Application API helper (same restart intent as SMC).
|
* Minimaler Sonic-Restart (wie SMC): JMSConnectorClient + MBean stop/restart.
|
||||||
*
|
|
||||||
* TOOL_VERSION is printed first so we can verify the deployed build.
|
|
||||||
*
|
|
||||||
* Remote JMSConnectorClient is "unbounded". IAgentProxy.restart() often throws
|
|
||||||
* "Operation unsupported for unbounded client connector". SMC Restart typically
|
|
||||||
* stops the agent via JMX; Launch Daemon brings it back.
|
|
||||||
*
|
|
||||||
* Compile: javac --release 8 SonicMfContainerTool.java
|
* Compile: javac --release 8 SonicMfContainerTool.java
|
||||||
*/
|
*/
|
||||||
public final class SonicMfContainerTool
|
public final class SonicMfContainerTool
|
||||||
{
|
{
|
||||||
/** Bump when restart logic changes – must appear in app output. */
|
public static final String TOOL_VERSION = "2026-07-24d-restart-only";
|
||||||
public static final String TOOL_VERSION = "2026-07-24c-mbean-stop";
|
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
@@ -29,21 +17,14 @@ public final class SonicMfContainerTool
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Args a = Args.parse(args);
|
Args a = Args.parse(args);
|
||||||
if (a.command == null)
|
if (!"restart".equals(a.command))
|
||||||
{
|
{
|
||||||
fail("Usage: SonicMfContainerTool ping|list|restart --domain D --url U --user U [--password P] [--container C] [--timeout SEC]");
|
fail("Usage: SonicMfContainerTool restart --domain D --url U --user U --container C [--timeout SEC]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isBlank(a.domain) || isBlank(a.url) || isBlank(a.user) || isBlank(a.container))
|
||||||
if (isBlank(a.domain) || isBlank(a.url) || isBlank(a.user))
|
|
||||||
{
|
{
|
||||||
fail("domain, url und user sind Pflicht.");
|
fail("domain, url, user und container sind Pflicht.");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("restart".equals(a.command) && isBlank(a.container))
|
|
||||||
{
|
|
||||||
fail("restart erfordert --container.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,22 +32,7 @@ public final class SonicMfContainerTool
|
|||||||
Object connector = connect(a.url, a.user, a.password, timeoutMs);
|
Object connector = connect(a.url, a.user, a.password, timeoutMs);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if ("ping".equals(a.command))
|
restart(connector, a.domain, a.container);
|
||||||
{
|
|
||||||
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
|
finally
|
||||||
{
|
{
|
||||||
@@ -86,8 +52,7 @@ public final class SonicMfContainerTool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object connect(String url, String user, String password, long timeoutMs)
|
private static Object connect(String url, String user, String password, long timeoutMs) throws Exception
|
||||||
throws Exception
|
|
||||||
{
|
{
|
||||||
Hashtable env = new Hashtable();
|
Hashtable env = new Hashtable();
|
||||||
env.put("ConnectionURLs", url);
|
env.put("ConnectionURLs", url);
|
||||||
@@ -99,8 +64,6 @@ public final class SonicMfContainerTool
|
|||||||
|
|
||||||
Class clientCl = Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient");
|
Class clientCl = Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient");
|
||||||
Object connector = clientCl.getConstructor(new Class[] {}).newInstance(new Object[] {});
|
Object connector = clientCl.getConstructor(new Class[] {}).newInstance(new Object[] {});
|
||||||
|
|
||||||
// Optional: Request-Timeout setzen (falls vorhanden)
|
|
||||||
trySetTimeout(connector, timeoutMs);
|
trySetTimeout(connector, timeoutMs);
|
||||||
|
|
||||||
Method connect = findConnect(clientCl);
|
Method connect = findConnect(clientCl);
|
||||||
@@ -111,199 +74,34 @@ public final class SonicMfContainerTool
|
|||||||
}
|
}
|
||||||
|
|
||||||
Class[] pts = connect.getParameterTypes();
|
Class[] pts = connect.getParameterTypes();
|
||||||
Object[] callArgs;
|
|
||||||
if (pts.length == 2)
|
if (pts.length == 2)
|
||||||
{
|
{
|
||||||
callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) };
|
connect.invoke(connector, new Object[] { address, boxTimeout(pts[1], timeoutMs) });
|
||||||
}
|
|
||||||
else if (pts.length == 1)
|
|
||||||
{
|
|
||||||
callArgs = new Object[] { address };
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
callArgs = new Object[] { address, boxTimeout(pts[1], timeoutMs) };
|
connect.invoke(connector, new Object[] { address });
|
||||||
}
|
}
|
||||||
|
|
||||||
connect.invoke(connector, callArgs);
|
|
||||||
info("Connected url=" + url + " user=" + user);
|
|
||||||
return connector;
|
return connector;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Method findConnect(Class clientCl)
|
|
||||||
{
|
|
||||||
Method best = null;
|
|
||||||
Method[] methods = clientCl.getMethods();
|
|
||||||
for (int i = 0; i < methods.length; i++)
|
|
||||||
{
|
|
||||||
Method m = methods[i];
|
|
||||||
if (!"connect".equals(m.getName()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (m.getParameterTypes().length == 2)
|
|
||||||
{
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
if (m.getParameterTypes().length == 1)
|
|
||||||
{
|
|
||||||
best = m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void trySetTimeout(Object connector, long timeoutMs)
|
|
||||||
{
|
|
||||||
String[] names = new String[] { "setTimeout", "setRequestTimeout", "setDefaultTimeout" };
|
|
||||||
for (int i = 0; i < names.length; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Method m = connector.getClass().getMethod(names[i], new Class[] { Long.TYPE });
|
|
||||||
m.invoke(connector, new Object[] { Long.valueOf(timeoutMs) });
|
|
||||||
info("Set " + names[i] + "=" + timeoutMs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (NoSuchMethodException ignore)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Method m = connector.getClass().getMethod(names[i], new Class[] { Integer.TYPE });
|
|
||||||
m.invoke(connector, new Object[] { Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs)) });
|
|
||||||
info("Set " + names[i] + "=" + timeoutMs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ignore2)
|
|
||||||
{
|
|
||||||
/* next */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ignore)
|
|
||||||
{
|
|
||||||
/* next */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object boxTimeout(Class type, long timeoutMs)
|
|
||||||
{
|
|
||||||
if (type == Long.TYPE || type == Long.class)
|
|
||||||
{
|
|
||||||
return Long.valueOf(timeoutMs);
|
|
||||||
}
|
|
||||||
if (type == Integer.TYPE || type == Integer.class)
|
|
||||||
{
|
|
||||||
return Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs));
|
|
||||||
}
|
|
||||||
return Long.valueOf(timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ping(Object connector, String domain) throws Exception
|
|
||||||
{
|
|
||||||
Set names = queryAgents(connector);
|
|
||||||
int matched = 0;
|
|
||||||
if (names != null)
|
|
||||||
{
|
|
||||||
for (Iterator it = names.iterator(); it.hasNext(); )
|
|
||||||
{
|
|
||||||
ObjectName on = (ObjectName) it.next();
|
|
||||||
if (extractContainer(on, domain) != null)
|
|
||||||
{
|
|
||||||
matched++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
System.out.println("OK:MfApiPing domain=" + domain + " agents=" + matched);
|
|
||||||
System.out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void list(Object connector, String domain) throws Exception
|
|
||||||
{
|
|
||||||
Set names = queryAgents(connector);
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
info("KeineAgentContainer Domain=" + domain);
|
|
||||||
}
|
|
||||||
System.out.println("OK:List count=" + count);
|
|
||||||
System.out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void restart(Object connector, String domain, String container) throws Exception
|
private static void restart(Object connector, String domain, String container) throws Exception
|
||||||
{
|
{
|
||||||
String shortName = shortContainer(container);
|
String shortName = shortContainer(container);
|
||||||
// Zuerst Preferred-ObjectName OHNE queryNames – query kann schon scheitern/irreführen
|
ObjectName on = new ObjectName(domain + "." + shortName + ":ID=AGENT");
|
||||||
ObjectName preferred = new ObjectName(domain + "." + shortName + ":ID=AGENT");
|
|
||||||
info("PreferredObjectName=" + preferred);
|
|
||||||
|
|
||||||
ObjectName on = preferred;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ObjectName resolved = resolveAgentObjectName(connector, domain, shortName);
|
|
||||||
if (resolved != null)
|
|
||||||
{
|
|
||||||
on = resolved;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
warn("resolveAgentObjectName: " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage()
|
|
||||||
+ " – nutze PreferredObjectName");
|
|
||||||
}
|
|
||||||
info("ObjectName=" + on);
|
info("ObjectName=" + on);
|
||||||
|
|
||||||
dumpOpsSafe(connector, on);
|
|
||||||
|
|
||||||
StringBuffer attempts = new StringBuffer();
|
StringBuffer attempts = new StringBuffer();
|
||||||
|
String[] ops = new String[] { "stop", "restart" };
|
||||||
// 1) JMX invoke stop/restart/shutdown (remote / wie SMC)
|
|
||||||
if (tryMBeanLifecycle(connector, on, shortName, domain, attempts))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) IAgentProxy stop/restart
|
|
||||||
if (tryAgentProxyLifecycle(connector, on, shortName, domain, attempts))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Manager-MBeans mit Container-Namen
|
|
||||||
if (tryManagerContainerOps(connector, domain, shortName, attempts))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fail("Kein Neustart-Weg erfolgreich. ToolVersion=" + TOOL_VERSION + "\nAttempts:\n" + attempts.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean tryMBeanLifecycle(
|
|
||||||
Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts)
|
|
||||||
{
|
|
||||||
String[] ops = new String[] { "stop", "restart", "shutdown" };
|
|
||||||
for (int i = 0; i < ops.length; i++)
|
for (int i = 0; i < ops.length; i++)
|
||||||
{
|
{
|
||||||
String op = ops[i];
|
String op = ops[i];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
info("Trying MBean.invoke(" + op + ") on " + on);
|
info("Trying MBean.invoke(" + op + ")");
|
||||||
invokeNoArgs(connector, on, op);
|
invokeNoArgs(connector, on, op);
|
||||||
okRestart("MBean." + op, shortName, domain);
|
okRestart("MBean." + op, shortName, domain);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
@@ -311,162 +109,23 @@ public final class SonicMfContainerTool
|
|||||||
String line = "MBean." + op + " => " + root.getClass().getName() + ": " + root.getMessage();
|
String line = "MBean." + op + " => " + root.getClass().getName() + ": " + root.getMessage();
|
||||||
warn(line);
|
warn(line);
|
||||||
attempts.append(line).append('\n');
|
attempts.append(line).append('\n');
|
||||||
|
|
||||||
// stop kann die Verbindung reissen – wenn Message darauf hindeutet, als Erfolg werten
|
|
||||||
if ("stop".equals(op) && looksLikeStopSideEffect(root))
|
if ("stop".equals(op) && looksLikeStopSideEffect(root))
|
||||||
{
|
{
|
||||||
warn("Treat stop side-effect as success (agent going down)");
|
|
||||||
okRestart("MBean.stop(side-effect)", shortName, domain);
|
okRestart("MBean.stop(side-effect)", shortName, domain);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
fail("Neustart fehlgeschlagen. ToolVersion=" + TOOL_VERSION + "\nAttempts:\n" + attempts.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean tryAgentProxyLifecycle(
|
private static void invokeNoArgs(Object connector, ObjectName on, String op) throws Exception
|
||||||
Object connector, ObjectName on, String shortName, String domain, StringBuffer attempts)
|
|
||||||
{
|
{
|
||||||
try
|
Method invoke = connector.getClass().getMethod("invoke", new Class[] {
|
||||||
{
|
ObjectName.class, String.class, Object[].class, String[].class
|
||||||
Class factoryCl = Class.forName("com.sonicsw.mf.mgmtapi.runtime.MFProxyFactory");
|
});
|
||||||
Method create = factoryCl.getMethod("createAgentProxy", new Class[] {
|
invoke.invoke(connector, new Object[] { on, op, new Object[0], new String[0] });
|
||||||
Class.forName("com.sonicsw.mf.jmx.client.JMSConnectorClient"),
|
|
||||||
ObjectName.class
|
|
||||||
});
|
|
||||||
Object agent = create.invoke(null, new Object[] { connector, on });
|
|
||||||
info("StateBefore=" + safeState(agent));
|
|
||||||
|
|
||||||
String[] methods = new String[] { "stop", "restart", "shutdown" };
|
|
||||||
for (int i = 0; i < methods.length; i++)
|
|
||||||
{
|
|
||||||
String mName = methods[i];
|
|
||||||
try
|
|
||||||
{
|
|
||||||
info("Trying IAgentProxy." + mName);
|
|
||||||
agent.getClass().getMethod(mName).invoke(agent);
|
|
||||||
okRestart("IAgentProxy." + mName, shortName, domain);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
Throwable root = rootOf(t);
|
|
||||||
String line = "IAgentProxy." + mName + " => " + root.getClass().getName() + ": " + root.getMessage();
|
|
||||||
warn(line);
|
|
||||||
attempts.append(line).append('\n');
|
|
||||||
if ("stop".equals(mName) && looksLikeStopSideEffect(root))
|
|
||||||
{
|
|
||||||
okRestart("IAgentProxy.stop(side-effect)", shortName, domain);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
Throwable root = rootOf(t);
|
|
||||||
String line = "IAgentProxy path => " + root.getClass().getName() + ": " + root.getMessage();
|
|
||||||
warn(line);
|
|
||||||
attempts.append(line).append('\n');
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean tryManagerContainerOps(
|
|
||||||
Object connector, String domain, String shortName, StringBuffer attempts)
|
|
||||||
{
|
|
||||||
String[] candidates = new String[] {
|
|
||||||
domain + ".DomainManager:ID=DomainManager",
|
|
||||||
domain + ".DomainManager:ID=AGENT",
|
|
||||||
domain + ".dm:ID=AGENT"
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < candidates.length; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ObjectName pattern = new ObjectName(candidates[i]);
|
|
||||||
Set found = queryNames(connector, pattern);
|
|
||||||
if (found == null || found.isEmpty())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ObjectName real = (ObjectName) found.iterator().next();
|
|
||||||
info("Manager=" + real);
|
|
||||||
dumpOpsSafe(connector, real);
|
|
||||||
|
|
||||||
String[] ops = new String[] { "restartContainer", "stopContainer", "startContainer" };
|
|
||||||
for (int o = 0; o < ops.length; o++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
info("Trying " + ops[o] + "(\"" + shortName + "\") on " + real);
|
|
||||||
invokeStringArg(connector, real, ops[o], shortName);
|
|
||||||
okRestart("Manager." + ops[o], shortName, domain);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
Throwable root = rootOf(t);
|
|
||||||
String line = ops[o] + " => " + root.getClass().getName() + ": " + root.getMessage();
|
|
||||||
warn(line);
|
|
||||||
attempts.append(line).append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
warn("Manager candidate " + candidates[i] + ": " + rootOf(t).getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan AGENT-MBeans nach Container-Ops
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Set agents = queryAgents(connector);
|
|
||||||
if (agents == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (Iterator it = agents.iterator(); it.hasNext(); )
|
|
||||||
{
|
|
||||||
ObjectName on = (ObjectName) it.next();
|
|
||||||
String jmxDomain = on.getDomain();
|
|
||||||
if (jmxDomain == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String lower = jmxDomain.toLowerCase();
|
|
||||||
if (!(lower.contains("domainmanager") || lower.contains(".dm")
|
|
||||||
|| lower.contains("collector") || lower.contains("host")
|
|
||||||
|| lower.contains("launch")))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
info("Scan manager agent " + on);
|
|
||||||
dumpOpsSafe(connector, on);
|
|
||||||
String[] ops = new String[] { "restartContainer", "stopContainer" };
|
|
||||||
for (int i = 0; i < ops.length; i++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
invokeStringArg(connector, on, ops[i], shortName);
|
|
||||||
okRestart(ops[i] + "@" + on, shortName, domain);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
attempts.append(ops[i]).append('@').append(on).append(" => ")
|
|
||||||
.append(rootOf(t).getMessage()).append('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
warn("Manager scan: " + rootOf(t).getMessage());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean looksLikeStopSideEffect(Throwable root)
|
private static boolean looksLikeStopSideEffect(Throwable root)
|
||||||
@@ -479,8 +138,7 @@ public final class SonicMfContainerTool
|
|||||||
return m.indexOf("disconnect") >= 0
|
return m.indexOf("disconnect") >= 0
|
||||||
|| m.indexOf("closed") >= 0
|
|| m.indexOf("closed") >= 0
|
||||||
|| m.indexOf("not connected") >= 0
|
|| m.indexOf("not connected") >= 0
|
||||||
|| m.indexOf("connection lost") >= 0
|
|| m.indexOf("connection lost") >= 0;
|
||||||
|| m.indexOf("broker") >= 0 && m.indexOf("down") >= 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void okRestart(String method, String shortName, String domain)
|
private static void okRestart(String method, String shortName, String domain)
|
||||||
@@ -491,172 +149,75 @@ public final class SonicMfContainerTool
|
|||||||
System.out.flush();
|
System.out.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void invokeNoArgs(Object connector, ObjectName on, String op) throws Exception
|
private static Method findConnect(Class clientCl)
|
||||||
{
|
{
|
||||||
Method invoke = connector.getClass().getMethod("invoke", new Class[] {
|
Method[] methods = clientCl.getMethods();
|
||||||
ObjectName.class, String.class, Object[].class, String[].class
|
Method best = null;
|
||||||
});
|
for (int i = 0; i < methods.length; i++)
|
||||||
invoke.invoke(connector, new Object[] { on, op, new Object[0], new String[0] });
|
{
|
||||||
|
Method m = methods[i];
|
||||||
|
if (!"connect".equals(m.getName()))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Class[] pts = m.getParameterTypes();
|
||||||
|
if (pts.length == 1 || pts.length == 2)
|
||||||
|
{
|
||||||
|
best = m;
|
||||||
|
if (pts.length == 2)
|
||||||
|
{
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void invokeStringArg(Object connector, ObjectName on, String op, String arg) throws Exception
|
private static void trySetTimeout(Object connector, long timeoutMs)
|
||||||
{
|
|
||||||
Method invoke = connector.getClass().getMethod("invoke", new Class[] {
|
|
||||||
ObjectName.class, String.class, Object[].class, String[].class
|
|
||||||
});
|
|
||||||
invoke.invoke(connector, new Object[] {
|
|
||||||
on, op, new Object[] { arg }, new String[] { "java.lang.String" }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void dumpOpsSafe(Object connector, ObjectName on)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Method getInfo = connector.getClass().getMethod("getMBeanInfo", new Class[] { ObjectName.class });
|
connector.getClass().getMethod("setTimeout", new Class[] { Long.TYPE })
|
||||||
MBeanInfo infoBean = (MBeanInfo) getInfo.invoke(connector, new Object[] { on });
|
.invoke(connector, new Object[] { Long.valueOf(timeoutMs) });
|
||||||
if (infoBean == null || infoBean.getOperations() == null)
|
}
|
||||||
|
catch (Exception ignore)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
info("Ops(" + on + ")=(null)");
|
connector.getClass().getMethod("setRequestTimeout", new Class[] { Long.TYPE })
|
||||||
return;
|
.invoke(connector, new Object[] { Long.valueOf(timeoutMs) });
|
||||||
}
|
}
|
||||||
StringBuffer sb = new StringBuffer();
|
catch (Exception ignore2)
|
||||||
MBeanOperationInfo[] ops = infoBean.getOperations();
|
|
||||||
for (int i = 0; i < ops.length; i++)
|
|
||||||
{
|
{
|
||||||
String name = ops[i].getName();
|
/* optional */
|
||||||
String lower = name.toLowerCase();
|
|
||||||
if (lower.indexOf("restart") >= 0 || lower.indexOf("stop") >= 0
|
|
||||||
|| lower.indexOf("start") >= 0 || lower.indexOf("shutdown") >= 0
|
|
||||||
|| lower.indexOf("container") >= 0)
|
|
||||||
{
|
|
||||||
if (sb.length() > 0)
|
|
||||||
{
|
|
||||||
sb.append(',');
|
|
||||||
}
|
|
||||||
sb.append(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info("Ops(" + on + ")=" + (sb.length() == 0 ? "(keine lifecycle)" : sb.toString()));
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
warn("getMBeanInfo(" + on + "): " + rootOf(t).getClass().getName() + ": " + rootOf(t).getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set queryAgents(Object connector) throws Exception
|
|
||||||
{
|
|
||||||
return queryNames(connector, new ObjectName("*:ID=AGENT"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set queryNames(Object connector, ObjectName pattern) throws Exception
|
|
||||||
{
|
|
||||||
Method query = connector.getClass().getMethod("queryNames", new Class[] {
|
|
||||||
ObjectName.class, javax.management.QueryExp.class
|
|
||||||
});
|
|
||||||
return (Set) query.invoke(connector, new Object[] { pattern, null });
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ObjectName resolveAgentObjectName(Object connector, String domain, String container)
|
|
||||||
throws Exception
|
|
||||||
{
|
|
||||||
ObjectName preferred = new ObjectName(domain + "." + container + ":ID=AGENT");
|
|
||||||
Set exact = queryNames(connector, preferred);
|
|
||||||
if (exact != null && !exact.isEmpty())
|
|
||||||
{
|
|
||||||
return (ObjectName) exact.iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
Set names = queryAgents(connector);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return preferred;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractContainer(ObjectName on, String domain)
|
private static Object boxTimeout(Class type, long timeoutMs)
|
||||||
{
|
{
|
||||||
if (on == null || isBlank(domain))
|
if (type == Integer.TYPE || type == Integer.class)
|
||||||
{
|
{
|
||||||
return null;
|
return Integer.valueOf((int) Math.min(Integer.MAX_VALUE, timeoutMs));
|
||||||
}
|
}
|
||||||
String jmxDomain = on.getDomain();
|
return Long.valueOf(timeoutMs);
|
||||||
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)
|
private static String shortContainer(String container)
|
||||||
{
|
{
|
||||||
if (container == null)
|
String c = container.trim();
|
||||||
{
|
int dot = c.lastIndexOf('.');
|
||||||
return "";
|
return dot >= 0 ? c.substring(dot + 1) : c;
|
||||||
}
|
|
||||||
int dot = container.indexOf('.');
|
|
||||||
if (dot >= 0 && dot < container.length() - 1)
|
|
||||||
{
|
|
||||||
return container.substring(dot + 1);
|
|
||||||
}
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String safeState(Object agent)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Object s = agent.getClass().getMethod("getStateString").invoke(agent);
|
|
||||||
return s == null ? "?" : String.valueOf(s);
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Throwable rootOf(Throwable t)
|
private static Throwable rootOf(Throwable t)
|
||||||
{
|
{
|
||||||
Throwable root = t;
|
Throwable cur = t;
|
||||||
while (root != null && root.getCause() != null && root.getCause() != root)
|
while (cur.getCause() != null && cur.getCause() != cur)
|
||||||
{
|
{
|
||||||
root = root.getCause();
|
cur = cur.getCause();
|
||||||
}
|
}
|
||||||
return root == null ? t : root;
|
return cur;
|
||||||
}
|
|
||||||
|
|
||||||
private static void info(String message)
|
|
||||||
{
|
|
||||||
System.out.println("INFO:" + message);
|
|
||||||
System.out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void warn(String message)
|
|
||||||
{
|
|
||||||
System.out.println("WARN:" + message);
|
|
||||||
System.out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void fail(String message)
|
|
||||||
{
|
|
||||||
System.err.println("ERROR:" + message);
|
|
||||||
System.err.flush();
|
|
||||||
System.exit(2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isBlank(String s)
|
private static boolean isBlank(String s)
|
||||||
@@ -664,6 +225,25 @@ public final class SonicMfContainerTool
|
|||||||
return s == null || s.trim().length() == 0;
|
return s == null || s.trim().length() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void info(String msg)
|
||||||
|
{
|
||||||
|
System.out.println("INFO:" + msg);
|
||||||
|
System.out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void warn(String msg)
|
||||||
|
{
|
||||||
|
System.out.println("WARN:" + msg);
|
||||||
|
System.out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void fail(String msg)
|
||||||
|
{
|
||||||
|
System.err.println("ERROR:" + msg);
|
||||||
|
System.err.flush();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
private static final class Args
|
private static final class Args
|
||||||
{
|
{
|
||||||
String command;
|
String command;
|
||||||
@@ -681,27 +261,22 @@ public final class SonicMfContainerTool
|
|||||||
{
|
{
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
a.command = args[0];
|
||||||
a.command = args[0].toLowerCase();
|
|
||||||
for (int i = 1; i < args.length; i++)
|
for (int i = 1; i < args.length; i++)
|
||||||
{
|
{
|
||||||
String key = args[i];
|
String k = args[i];
|
||||||
if (!key.startsWith("--") || i + 1 >= args.length)
|
String v = (i + 1 < args.length) ? args[i + 1] : null;
|
||||||
|
if ("--domain".equals(k) && v != null) { a.domain = v; i++; }
|
||||||
|
else if ("--url".equals(k) && v != null) { a.url = v; i++; }
|
||||||
|
else if ("--user".equals(k) && v != null) { a.user = v; i++; }
|
||||||
|
else if ("--password".equals(k) && v != null) { a.password = v; i++; }
|
||||||
|
else if ("--container".equals(k) && v != null) { a.container = v; i++; }
|
||||||
|
else if ("--timeout".equals(k) && v != null)
|
||||||
{
|
{
|
||||||
continue;
|
try { a.timeoutSec = Integer.parseInt(v); } catch (Exception ignore) { /* keep default */ }
|
||||||
}
|
i++;
|
||||||
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 */ }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isBlank(a.password))
|
if (isBlank(a.password))
|
||||||
{
|
{
|
||||||
String env = System.getenv("ESB_SONIC_PASSWORD");
|
String env = System.getenv("ESB_SONIC_PASSWORD");
|
||||||
@@ -713,4 +288,8 @@ public final class SonicMfContainerTool
|
|||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SonicMfContainerTool()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
||||||
"Username": "Administrator",
|
"Username": "Administrator",
|
||||||
"Password": "Administrator",
|
"Password": "Administrator",
|
||||||
"ManagementMode": "MfApi",
|
|
||||||
"SonicHome": "C:\\DEV\\MQ10.0",
|
"SonicHome": "C:\\DEV\\MQ10.0",
|
||||||
"JavaHome": "C:\\Program Files (x86)\\Java\\jre1.8.0_501",
|
"JavaHome": "C:\\Program Files (x86)\\Java\\jre1.8.0_501",
|
||||||
"JavaPath": "C:\\Program Files (x86)\\Java\\jre1.8.0_501\\bin\\java.exe",
|
"JavaPath": "C:\\Program Files (x86)\\Java\\jre1.8.0_501\\bin\\java.exe",
|
||||||
|
|||||||
Reference in New Issue
Block a user