Align DE-Test as container and verify Sonic restart via process check.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-24 08:25:26 +02:00
co-authored by Cursor
parent 0e9eb27119
commit f0ce6e1b3c
6 changed files with 212 additions and 107 deletions
@@ -2,53 +2,38 @@ namespace ZA.CoreService.ESBCertificateManager.Models;
/// <summary>
/// Verbindungskonfiguration für eine Progress Sonic ESB Management Instanz.
/// 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>
public sealed class SonicConnection
{
/// <summary>
/// Eindeutiger Bezeichner; wird in <see cref="DeploymentTarget.SonicConnectionName"/> referenziert.
/// Alias der Verbindung; referenziert von <see cref="DeploymentTarget.SonicConnectionName"/>.
/// </summary>
public required string Name { get; init; }
/// <summary>Sonic-Domain-Name, z.B. "proalpha-test".</summary>
/// <summary>Sonic-Domain, z.B. "proalpha-test" (nicht der Containername).</summary>
public required string DomainName { get; init; }
/// <summary>
/// Sonic-Broker-/Management-URL im Sonic-Format, z.B. "tcp://dekun-painwbdet:13070".
/// Der Hostname wird daraus extrahiert (WinRM-Ziel / HTTP-Basis).
/// Domain-Manager-/Broker-URL, z.B. "tcp://dekun-painwbdet:13070".
/// </summary>
public required string ConnectionUrl { get; init; }
/// <summary>Benutzername für WinRM / Management-Konsole.</summary>
public required string Username { get; init; }
/// <summary>Passwort für WinRM / Management-Konsole.</summary>
public required string Password { get; init; }
/// <summary>
/// Management-Modus:
/// - WinRm: PowerShell Remoting auf dem Sonic-Server (Standard)
/// - LocalCmd: CMD/PowerShell lokal (zum Testen direkt auf dem Sonic-PC)
/// - HttpApi: REST, falls vorhanden
/// </summary>
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.WinRm;
/// <summary>
/// Installationsroot von Sonic MQ / Management Console, z.B. "C:\Sonic\MQ10.0".
/// Wird für Default-Scripts (stopcontainer/startcontainer) benötigt.
/// </summary>
/// <summary>Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0".</summary>
public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0";
/// <summary>
/// Bekannte Container-Namen dieser Domain (Fallback, wenn Remote-Discovery nichts findet).
/// z.B. [ "ESB", "DomainManager" ] oder vollqualifiziert [ "proalpha-test.ESB" ].
/// Container-Namen (kurz), z.B. ["DE-Test"]. Nicht die Domain.
/// </summary>
public List<string> KnownContainers { get; init; } = [];
// ---------------------------------------------------------------
// HTTP REST API (ManagementMode = HttpApi)
// ---------------------------------------------------------------
public int ManagementHttpPort { get; init; } = 8080;
public string ApiBasePath { get; init; } = "/api/v1";
public string ContainerListPath { get; init; } = string.Empty;
@@ -56,43 +41,27 @@ public sealed class SonicConnection
public string ContainerStopPath { get; init; } = string.Empty;
public string ContainerStartPath { get; init; } = string.Empty;
// ---------------------------------------------------------------
// WinRM / LocalCmd
// ---------------------------------------------------------------
public int WinRmPort { get; init; } = 5985;
/// <summary>
/// PowerShell-Script zum Neustarten. Leer = Default über Sonic bin\stop/startcontainer.bat.
/// Platzhalter: {container}, {domain}, {sonicHome}
/// </summary>
public string WinRmRestartScript { get; init; } = string.Empty;
/// <summary>
/// PowerShell-Script zum Auflisten der Container. Leer = Default (Services + SonicHome).
/// </summary>
public string WinRmContainerListScript { get; init; } = string.Empty;
public string WinRmXapiImportScript { get; init; } = string.Empty;
public int TimeoutSeconds { get; init; } = 60;
public int PostRestartDelaySeconds { get; init; } = 15;
public int TimeoutSeconds { get; init; } = 120;
public int PostRestartDelaySeconds { get; init; } = 20;
/// <summary>Effektives Restart-Script inkl. Default, wenn leer.</summary>
public string ResolveRestartScript()
=> string.IsNullOrWhiteSpace(WinRmRestartScript)
? DefaultRestartScript
: WinRmRestartScript;
/// <summary>Effektives List-Script inkl. Default, wenn leer.</summary>
public string ResolveContainerListScript()
=> string.IsNullOrWhiteSpace(WinRmContainerListScript)
? DefaultContainerListScript
: WinRmContainerListScript;
/// <summary>
/// Default: Sonic Management Console Tools per CMD auf dem Ziel-PC.
/// stopcontainer.bat / startcontainer.bat unter SonicHome\bin.
/// Echter Neustart mit Prozess-Verifikation (sichtbar in SMC):
/// alte PIDs müssen weg, neue PIDs müssen erscheinen sonst Fehler.
/// </summary>
public const string DefaultRestartScript =
"""
@@ -100,33 +69,144 @@ public sealed class SonicConnection
$sonicHome = '{sonicHome}'
$domain = '{domain}'
$container = '{container}'
$connectionUrl = '{connectionUrl}'
$username = '{username}'
$password = '{password}'
$bin = Join-Path $sonicHome 'bin'
$stop = Join-Path $bin 'stopcontainer.bat'
$start = Join-Path $bin 'startcontainer.bat'
$fullName = if ($container -like '*.*') { $container } else { "$domain.$container" }
$stopBat = Join-Path $bin 'stopcontainer.bat'
$startBat = Join-Path $bin 'startcontainer.bat'
$esbAdmin = Join-Path $bin 'esbadmin.bat'
if (-not (Test-Path -LiteralPath $stop)) {
throw "Sonic stopcontainer.bat nicht gefunden: $stop (SonicHome prüfen)"
}
if (-not (Test-Path -LiteralPath $start)) {
throw "Sonic startcontainer.bat nicht gefunden: $start (SonicHome prüfen)"
# 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)
}
Write-Output "STOP $fullName via $stop"
$stopProc = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$stop`" `"$fullName`"") -Wait -PassThru -NoNewWindow
if ($stopProc.ExitCode -ne 0) {
Write-Warning "stopcontainer ExitCode=$($stopProc.ExitCode) starte trotzdem neu"
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
}
Start-Sleep -Seconds 5
Write-Output "START $fullName via $start"
$startProc = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$start`" `"$fullName`"") -Wait -PassThru -NoNewWindow
if ($startProc.ExitCode -ne 0) {
throw "startcontainer fehlgeschlagen, ExitCode=$($startProc.ExitCode)"
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 ,@()
}
Write-Output "Neustart ok: $fullName"
Write-Output "INFO:Domain=$domain Container=$shortName Full=$fullName"
$before = @(Get-ContainerPids)
Write-Output "INFO:PIDsVorher=$($before -join ',')"
if ($before.Count -eq 0) {
Write-Output "WARN:Kein laufender Java/Sonic-Prozess für '$shortName' gefunden Stop ggf. schon offline; starte trotzdem."
}
# 1) Optional: esbadmin (SMC-kompatibel über Domain Manager)
if (Test-Path -LiteralPath $esbAdmin) {
Write-Output "INFO:Versuche esbadmin Stop/Start"
$scriptText = "connect $domain $connectionUrl $username $password`r`nstop container $shortName`r`nstart container $shortName`r`nexit`r`n"
$tmp = [System.IO.Path]::GetTempFileName() + '.txt'
Set-Content -LiteralPath $tmp -Value $scriptText -Encoding ASCII
try {
$p = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$esbAdmin`" < `"$tmp`"") -Wait -PassThru -NoNewWindow
Write-Output "INFO:esbadmin ExitCode=$($p.ExitCode)"
}
finally {
Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 5
}
# 2) stopcontainer.bat
if (Test-Path -LiteralPath $stopBat) {
foreach ($n in @($fullName, $shortName)) {
Write-Output "INFO:stopcontainer $n"
$sp = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$stopBat`" `"$n`"") -Wait -PassThru -NoNewWindow
Write-Output "INFO:stopcontainer ExitCode=$($sp.ExitCode) Name=$n"
}
}
else {
Write-Output "WARN:stopcontainer.bat fehlt: $stopBat"
}
Start-Sleep -Seconds 3
$still = @(Get-ContainerPids)
# 3) Harter Stop der alten PIDs sonst sieht SMC oft keinen Neustart
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-Prozess läuft noch nach Stop (PIDs=$($before -join ',')). SMC würde keinen Stop sehen."
}
Write-Output "INFO:Alte PIDs beendet"
}
Start-Sleep -Seconds 3
# 4) Start
if (-not (Test-Path -LiteralPath $startBat)) {
throw "startcontainer.bat nicht gefunden: $startBat (SonicHome prüfen)"
}
$started = $false
foreach ($n in @($fullName, $shortName)) {
Write-Output "INFO:startcontainer $n"
$st = Start-Process -FilePath 'cmd.exe' -ArgumentList @('/c', "`"$startBat`" `"$n`"") -Wait -PassThru -NoNewWindow
Write-Output "INFO:startcontainer ExitCode=$($st.ExitCode) Name=$n"
if ($st.ExitCode -eq 0) { $started = $true; break }
}
if (-not $started) {
throw "startcontainer fehlgeschlagen für $fullName / $shortName"
}
$after = @(Wait-NewPids -oldPids $before -seconds 60)
if ($after.Count -eq 0) {
# falls Prozess mit gleicher PID-Liste zurückkam: mindestens irgendeinen Treffer verlangen
$any = @(Get-ContainerPids)
if ($any.Count -eq 0) {
throw "Nach Start kein Prozess für Container '$shortName' sichtbar. In SMC prüfen / SonicHome & ContainerName prüfen."
}
Write-Output "INFO:PIDsNachher=$($any -join ',')"
}
else {
Write-Output "INFO:PIDsNachher=$($after -join ',')"
}
Write-Output "OK:ContainerRestartVerified Domain=$domain Container=$shortName"
""";
public const string DefaultContainerListScript =
@@ -146,9 +226,8 @@ public sealed class SonicConnection
Write-Output "WARN:SonicHomeNichtGefunden:$sonicHome"
}
else {
Write-Output "INFO:SonicHomeOk:$sonicHome"
Write-Output "INFO:SonicHomeOk:$sonicHome Domain=$domain"
# *.cache Ordner (Domain.Container.cache) 12 Ebenen
Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue |
ForEach-Object {
if ($_.Name -like '*.cache') {
@@ -159,33 +238,33 @@ public sealed class SonicConnection
ForEach-Object { Add-Name ($_.Name -replace '\.cache$', '') }
}
# container.xml / Containers-Verzeichnisse
Get-ChildItem -LiteralPath $sonicHome -Recurse -Filter 'container.xml' -File -ErrorAction SilentlyContinue |
Select-Object -First 40 |
ForEach-Object {
$parent = Split-Path $_.DirectoryName -Leaf
Add-Name $parent
Add-Name (Split-Path $_.DirectoryName -Leaf)
}
}
# Windows-Dienste
Get-Service -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -match 'Sonic|ESB|MQ|Aurea' -or $_.Name -match 'Sonic|ESB|MQ|Aurea' } |
ForEach-Object {
Add-Name $_.Name
Add-Name $_.DisplayName
}
# Prozess-Hinweise (laufende Container)
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]
}
}
if ($names.Count -eq 0 -and -not [string]::IsNullOrWhiteSpace($domain)) {
# 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"
}
@@ -195,16 +274,7 @@ public sealed class SonicConnection
public enum SonicManagementMode
{
/// <summary>HTTP REST API (wenn vom Sonic-Server bereitgestellt).</summary>
HttpApi = 0,
/// <summary>
/// PowerShell Remoting (WinRM) führt Scripts auf dem Sonic-Server aus.
/// </summary>
WinRm = 1,
/// <summary>
/// CMD/PowerShell lokal auf diesem PC zum Testen direkt auf dem Sonic-Rechner.
/// </summary>
LocalCmd = 2
}