Make installer runtime acceptance transactional
This commit is contained in:
@@ -408,6 +408,17 @@ Task gestoppt und die Programmversion umgeschaltet. Scheitert ein Folgeschritt,
|
|||||||
versucht der Installer Programmverzeichnis, Checkmk-Wrapper und Scheduled Task
|
versucht der Installer Programmverzeichnis, Checkmk-Wrapper und Scheduled Task
|
||||||
auf den vorherigen Stand zurückzusetzen.
|
auf den vorherigen Stand zurückzusetzen.
|
||||||
|
|
||||||
|
Ab Version 2.2.4 ist die Umschaltung erst nach einer transaktionalen
|
||||||
|
Runtime-Abnahme abgeschlossen. Ein einmaliger, triggerloser Task läuft unter
|
||||||
|
dem echten Collector-Konto und erzwingt einen vollständigen Endpoint-
|
||||||
|
Katalogabgleich. Der Installer wartet begrenzt auf `LastTaskResult=0` und lässt
|
||||||
|
anschließend die installierte Produkt-EXE Snapshot und Katalog mit den normalen
|
||||||
|
Produktparsern validieren. Der Snapshot muss aus diesem Lauf stammen, die
|
||||||
|
erwartete Identität und alle neun eindeutigen Services ohne `UNKNOWN`
|
||||||
|
enthalten. Erst dann wird der normale Minutentask registriert und das Backup
|
||||||
|
gelöscht. Bei Fehler oder Timeout wird die alte Version wiederhergestellt und
|
||||||
|
ein frischer Lauf des alten Tasks abgewartet.
|
||||||
|
|
||||||
Der installierte Task selbst läuft mit `RunLevel Limited`. Der Installer
|
Der installierte Task selbst läuft mit `RunLevel Limited`. Der Installer
|
||||||
übergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle;
|
übergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle;
|
||||||
es wird weder in einer Prozesskommandozeile noch in Config oder Log
|
es wird weder in einer Prozesskommandozeile noch in Config oder Log
|
||||||
@@ -550,6 +561,8 @@ BizTalk-Probes:
|
|||||||
| Key | Default |
|
| Key | Default |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `Server` | `.` |
|
| `Server` | `.` |
|
||||||
|
| `EnvironmentName` | leer |
|
||||||
|
| `IncludeEnvironmentInServiceName` | `false` |
|
||||||
| `QueryTimeoutSeconds` | `25` |
|
| `QueryTimeoutSeconds` | `25` |
|
||||||
| `ProbeSqlConnectivity` | `true` |
|
| `ProbeSqlConnectivity` | `true` |
|
||||||
| `SqlConnectionTimeoutSeconds` | `5` |
|
| `SqlConnectionTimeoutSeconds` | `5` |
|
||||||
@@ -602,6 +615,11 @@ Automatisiert:
|
|||||||
einem theoretischen Socket-Budget von etwa 15 Sekunden; das konfigurierte
|
einem theoretischen Socket-Budget von etwa 15 Sekunden; das konfigurierte
|
||||||
Maximum von 100 Zielen in etwa 21 Sekunden,
|
Maximum von 100 Zielen in etwa 21 Sekunden,
|
||||||
- Update-Config-Merge erhält bestehende Werte und fügt neue Defaults hinzu,
|
- Update-Config-Merge erhält bestehende Werte und fügt neue Defaults hinzu,
|
||||||
|
- erzwungener Installations-Katalogabgleich wird unabhängig vom Katalogalter
|
||||||
|
ausgeführt,
|
||||||
|
- Runtime-Abnahme weist alten/manipulierten Snapshot, falsche Identität,
|
||||||
|
fehlenden Service, `UNKNOWN`, alten/ungültigen Katalog und fehlerhaften oder
|
||||||
|
nicht rechtzeitig beendeten Tasklauf zurück,
|
||||||
- Snapshot-Roundtrip und Ersatz,
|
- Snapshot-Roundtrip und Ersatz,
|
||||||
- SHA-256-Manipulation wird verworfen,
|
- SHA-256-Manipulation wird verworfen,
|
||||||
- Stale-Snapshot wird verworfen,
|
- Stale-Snapshot wird verworfen,
|
||||||
|
|||||||
@@ -141,8 +141,11 @@ Pro Service werden standardmäßig maximal fünf betroffene Namen gezeigt.
|
|||||||
Weitere Treffer erscheinen nur als `(+n more)`; Details sind zusätzlich auf
|
Weitere Treffer erscheinen nur als `(+n more)`; Details sind zusätzlich auf
|
||||||
1600 Zeichen begrenzt. Metriken bleiben trotzdem vollständig.
|
1600 Zeichen begrenzt. Metriken bleiben trotzdem vollständig.
|
||||||
|
|
||||||
Mit `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` wird die Umgebung in den
|
`EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` kennzeichnet Snapshot und
|
||||||
Servicenamen aufgenommen, zum Beispiel `BizTalk ACC Platform`.
|
Endpoint-Katalog. Die neun Checkmk-Servicenamen bleiben standardmäßig
|
||||||
|
unverändert. Nur das explizite Opt-in
|
||||||
|
`IncludeEnvironmentInServiceName=true` ergänzt die Umgebung im Servicenamen
|
||||||
|
und verlangt anschließend eine Checkmk Service Discovery.
|
||||||
|
|
||||||
Statuslogik und Metriken: [docs/CheckmkServices.md](docs/CheckmkServices.md)
|
Statuslogik und Metriken: [docs/CheckmkServices.md](docs/CheckmkServices.md)
|
||||||
|
|
||||||
@@ -217,7 +220,9 @@ artifacts\BizTalkCheckmkPulse-Setup\application\BizTalkCheckmkPulse.exe --self-t
|
|||||||
|
|
||||||
Erwartet werden exakt neun `OK`-Zeilen. Die Regressionstests prüfen
|
Erwartet werden exakt neun `OK`-Zeilen. Die Regressionstests prüfen
|
||||||
zusätzlich Snapshot-Roundtrip, atomaren Ersatz, SHA-256-Manipulation,
|
zusätzlich Snapshot-Roundtrip, atomaren Ersatz, SHA-256-Manipulation,
|
||||||
Stale-Erkennung, stabile Fallbacks und die bestehenden BizTalk-WMI-Diagnosen.
|
Stale-Erkennung, stabile Fallbacks, den exakten Servicevertrag, erzwungene
|
||||||
|
Katalogaktualisierung sowie erfolgreiche und abgewiesene
|
||||||
|
Post-Install-Runtime-Abnahmen.
|
||||||
Ein Mono-Build ist eine hilfreiche Quellcodeprüfung, ersetzt aber nicht die
|
Ein Mono-Build ist eine hilfreiche Quellcodeprüfung, ersetzt aber nicht die
|
||||||
Windows-/BizTalk-Laufzeitvalidierung.
|
Windows-/BizTalk-Laufzeitvalidierung.
|
||||||
|
|
||||||
@@ -265,17 +270,26 @@ Der Installer:
|
|||||||
2. baut die neue Version in einem Staging-Verzeichnis auf und übernimmt
|
2. baut die neue Version in einem Staging-Verzeichnis auf und übernimmt
|
||||||
vorhandene AppSettings; neue Keys erhalten ihre neuen Defaults. Die alten
|
vorhandene AppSettings; neue Keys erhalten ihre neuen Defaults. Die alten
|
||||||
unveränderten Endpoint-Defaults `12`/`500` werden auf `16`/`100` migriert,
|
unveränderten Endpoint-Defaults `12`/`500` werden auf `16`/`100` migriert,
|
||||||
3. schaltet die Programmdateien per Verzeichniswechsel um; bei einem Fehler
|
3. vergleicht installierten und neuen Checkmk-Servicevertrag; ein
|
||||||
werden vorherige Version, Wrapper und Task best effort wiederhergestellt,
|
unbestätigter Rename stoppt vor jeder Umschaltung,
|
||||||
4. behält `%ProgramData%\BizTalkCheckmkPulse` mit Endpoint-Katalog, Snapshot
|
4. schaltet die Programmdateien per Verzeichniswechsel um,
|
||||||
|
5. startet einen triggerlosen Provider-Abnahmelauf unter dem echten
|
||||||
|
Collector-Konto und erzwingt einen vollständigen Endpoint-Katalogabgleich,
|
||||||
|
6. wartet höchstens vier Minuten auf `LastTaskResult=0` und validiert danach
|
||||||
|
mit der Produkt-EXE einen erst nach Installationsbeginn erzeugten Snapshot,
|
||||||
|
die Collector-Identität, alle neun eindeutigen Services ohne `UNKNOWN`
|
||||||
|
sowie den frischen, maschinen- und umgebungsgebundenen Endpoint-Katalog,
|
||||||
|
7. registriert erst nach dieser Abnahme den normalen Minutentask; andernfalls
|
||||||
|
werden vorherige Version, Wrapper und Task samt frischem Altsnapshot
|
||||||
|
wiederhergestellt,
|
||||||
|
8. behält `%ProgramData%\BizTalkCheckmkPulse` mit Endpoint-Katalog, Snapshot
|
||||||
und Logs bei,
|
und Logs bei,
|
||||||
5. installiert EXE und Config nach `%ProgramFiles%\BizTalkCheckmkPulse`,
|
9. installiert EXE und Config nach `%ProgramFiles%\BizTalkCheckmkPulse`, setzt
|
||||||
6. setzt explizite ACLs für Administratoren, Provider und `LocalSystem`,
|
explizite ACLs für Administratoren, Provider und `LocalSystem` und
|
||||||
7. installiert nur den kleinen `.cmd`-Consumer unter
|
installiert nur den kleinen `.cmd`-Consumer unter
|
||||||
`%ProgramData%\checkmk\agent\local`,
|
`%ProgramData%\checkmk\agent\local`,
|
||||||
8. registriert `BizTalk Checkmk Pulse Provider` minütlich mit
|
10. registriert `BizTalk Checkmk Pulse Provider` minütlich mit `IgnoreNew`,
|
||||||
`IgnoreNew`, fünf Minuten Laufzeitlimit und zwei Wiederholungen,
|
fünf Minuten Laufzeitlimit und zwei Wiederholungen.
|
||||||
9. führt den installierten Self-Test erneut aus und startet den Provider.
|
|
||||||
|
|
||||||
Installation, Update, Deinstallation und Laufzeit verwenden keine PowerShell.
|
Installation, Update, Deinstallation und Laufzeit verwenden keine PowerShell.
|
||||||
|
|
||||||
@@ -356,6 +370,7 @@ Wichtige Werte:
|
|||||||
| Key | Default | Bedeutung |
|
| Key | Default | Bedeutung |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST`, `PRD`. |
|
| `EnvironmentName` | leer | Optional `ACC`, `DEV`, `TST`, `PRD`. |
|
||||||
|
| `IncludeEnvironmentInServiceName` | `false` | Nur mit geplanter Service Discovery aktivieren. |
|
||||||
| `SnapshotPath` | `%ProgramData%\BizTalkCheckmkPulse\data\...` | Gemeinsame Provider-/Consumer-Datei. |
|
| `SnapshotPath` | `%ProgramData%\BizTalkCheckmkPulse\data\...` | Gemeinsame Provider-/Consumer-Datei. |
|
||||||
| `SnapshotMaxAgeSeconds` | `180` | Ab diesem Alter liefert der Consumer `UNKNOWN`. |
|
| `SnapshotMaxAgeSeconds` | `180` | Ab diesem Alter liefert der Consumer `UNKNOWN`. |
|
||||||
| `SnapshotMaxBytes` | `1048576` | Harte Eingabegröße für den Consumer. |
|
| `SnapshotMaxBytes` | `1048576` | Harte Eingabegröße für den Consumer. |
|
||||||
|
|||||||
@@ -36,6 +36,25 @@ Sicherheitsstopp. Die Installation darf nur über die Checkbox
|
|||||||
fortgesetzt werden. Der Installer zeigt dabei die entfernten und neuen Namen
|
fortgesetzt werden. Der Installer zeigt dabei die entfernten und neuen Namen
|
||||||
an. Eine bloß geänderte Ausgabereihenfolge gilt nicht als Rename.
|
an. Eine bloß geänderte Ausgabereihenfolge gilt nicht als Rename.
|
||||||
|
|
||||||
|
Version 2.2.4 nimmt die umgeschaltete Installation zusätzlich vollständig ab:
|
||||||
|
|
||||||
|
- einmaliger, triggerloser Providerlauf unter dem angegebenen Collector-Konto,
|
||||||
|
- erzwungener vollständiger Abgleich von `endpoints.xml`, wobei gültige
|
||||||
|
manuelle Overrides erhalten bleiben,
|
||||||
|
- maximal vier Minuten Warten auf `LastTaskResult=0`,
|
||||||
|
- Snapshot muss nach Beginn des Abnahmelaufs erzeugt, SHA-256-valid, für die
|
||||||
|
aktuelle Maschine und vom erwarteten Collector-Konto geschrieben sein,
|
||||||
|
- alle neun stabilen Services müssen eindeutig vorhanden sein; `WARN` und
|
||||||
|
`CRIT` sind reale Betriebszustände, `UNKNOWN` blockiert die Installation,
|
||||||
|
- bei aktivierter Endpoint-Prüfung muss auch der Katalog nach Beginn des
|
||||||
|
Abnahmelaufs atomar synchronisiert worden sein.
|
||||||
|
|
||||||
|
Erst danach ersetzt der Installer den Abnahmetask durch den normalen
|
||||||
|
Minutentask und löscht das Backup. Bei Fehler oder Timeout wird die vorherige
|
||||||
|
Programmversion samt Task wiederhergestellt und ein frischer Lauf der alten
|
||||||
|
Version abgewartet. Fehler in Paket-, Config- oder Rename-Vorprüfung treten vor
|
||||||
|
jeder Taskänderung auf und lassen die laufende Installation unangetastet.
|
||||||
|
|
||||||
Beim korrigierenden Wechsel von 2.2.1 mit `BizTalk ACC ...` auf stabile
|
Beim korrigierenden Wechsel von 2.2.1 mit `BizTalk ACC ...` auf stabile
|
||||||
`BizTalk ...`-Namen ist die Änderung beabsichtigt: Checkbox aktivieren und den
|
`BizTalk ...`-Namen ist die Änderung beabsichtigt: Checkbox aktivieren und den
|
||||||
Servicebestand danach per Discovery abgleichen. Sind in Checkmk bereits die
|
Servicebestand danach per Discovery abgleichen. Sind in Checkmk bereits die
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ nicht netzwerkprüfbare Kandidaten. Version 2.2.2 weist sie explizit als
|
|||||||
tatsächliche Servicevertrag der vorhandenen und der neuen EXE wird vor dem
|
tatsächliche Servicevertrag der vorhandenen und der neuen EXE wird vor dem
|
||||||
Task-Stopp verglichen. Unbestätigte Renames, neue oder entfernte Services
|
Task-Stopp verglichen. Unbestätigte Renames, neue oder entfernte Services
|
||||||
blockieren das Update; die Differenz wird vollständig angezeigt.
|
blockieren das Update; die Differenz wird vollständig angezeigt.
|
||||||
|
- Version 2.2.4 erzwingt nach der Umschaltung einen frischen vollständigen
|
||||||
|
Endpoint-Katalogabgleich und akzeptiert die Installation erst nach einem
|
||||||
|
erfolgreichen Providerlauf mit neuem, identitäts- und integritätsgeprüftem
|
||||||
|
Snapshot sowie neun verlässlichen stabilen Services.
|
||||||
|
|
||||||
### Korrekte Abdeckungssemantik
|
### Korrekte Abdeckungssemantik
|
||||||
|
|
||||||
@@ -135,7 +139,7 @@ Endpoint-/Namensfix bewusst nicht grüngefärbt.
|
|||||||
|
|
||||||
## ACC-Abnahme nach Installation
|
## ACC-Abnahme nach Installation
|
||||||
|
|
||||||
1. Setup 2.2.3 als Update ausführen; Umgebung `ACC` kann bestehen bleiben.
|
1. Setup 2.2.4 als Update ausführen; Umgebung `ACC` kann bestehen bleiben.
|
||||||
Beim Wechsel von der fehlerhaften 2.2.1-Ausgabe `BizTalk ACC ...` auf die
|
Beim Wechsel von der fehlerhaften 2.2.1-Ausgabe `BizTalk ACC ...` auf die
|
||||||
stabilen Namen die Rename-/Discovery-Checkbox bewusst aktivieren.
|
stabilen Namen die Rename-/Discovery-Checkbox bewusst aktivieren.
|
||||||
2. In der installierten Config bestätigen:
|
2. In der installierten Config bestätigen:
|
||||||
|
|||||||
Binary file not shown.
@@ -15,6 +15,12 @@ Abgleich alle `EndpointDiscoveryIntervalHours` (Default: 168 Stunden). Ein
|
|||||||
Fehler beim Abgleich ersetzt keinen letzten gültigen Katalog. Checkmk meldet
|
Fehler beim Abgleich ersetzt keinen letzten gültigen Katalog. Checkmk meldet
|
||||||
den überfälligen/fehlgeschlagenen Abgleich trotzdem als `UNKNOWN`.
|
den überfälligen/fehlgeschlagenen Abgleich trotzdem als `UNKNOWN`.
|
||||||
|
|
||||||
|
Bei Installation oder Update ab Version 2.2.4 erzwingt ein einmaliger
|
||||||
|
Provider-Abnahmelauf unabhängig vom Alter einen vollständigen Abgleich. Der
|
||||||
|
Installer akzeptiert den Release erst, wenn der neue Synchronisationszeitpunkt
|
||||||
|
nach dem Start dieses Laufs liegt. Gültige manuelle Overrides werden dabei
|
||||||
|
erhalten; der Katalog wird nicht blind gelöscht.
|
||||||
|
|
||||||
Unabhängig vom Wochenabgleich wird bei jedem Minutenlauf gegen den aktuellen
|
Unabhängig vom Wochenabgleich wird bei jedem Minutenlauf gegen den aktuellen
|
||||||
BizTalk-Runtimezustand gefiltert:
|
BizTalk-Runtimezustand gefiltert:
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,17 @@ Verbindlicher Agent-Dump:
|
|||||||
|
|
||||||
## Service Discovery
|
## Service Discovery
|
||||||
|
|
||||||
|
Das Setup 2.2.4 schließt erst erfolgreich ab, nachdem der erste Providerlauf
|
||||||
|
unter dem echten Collector-Konto einen frischen Snapshot und – bei aktivierter
|
||||||
|
Endpoint-Prüfung – einen frisch synchronisierten Katalog erzeugt hat. Die neun
|
||||||
|
stabilen Services müssen vollständig und ohne `UNKNOWN` vorliegen. Diese lokale
|
||||||
|
Runtime-Abnahme ersetzt nicht die zentrale Checkmk Service Discovery.
|
||||||
|
|
||||||
|
`EnvironmentName` kennzeichnet Snapshot und Katalog, ändert die neun
|
||||||
|
Servicenamen aber standardmäßig nicht. Nur das bewusste Opt-in
|
||||||
|
`IncludeEnvironmentInServiceName=true` erzeugt umgebungsabhängige Namen und
|
||||||
|
verlangt den anschließenden Discovery-Abgleich.
|
||||||
|
|
||||||
1. Provider dreimal erfolgreich laufen lassen.
|
1. Provider dreimal erfolgreich laufen lassen.
|
||||||
2. `LastTaskResult=0`, frischen Snapshot und Log prüfen.
|
2. `LastTaskResult=0`, frischen Snapshot und Log prüfen.
|
||||||
3. Agent-Dump im `LocalSystem`-Kontext prüfen.
|
3. Agent-Dump im `LocalSystem`-Kontext prüfen.
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ def configure_document(document):
|
|||||||
p = footer.paragraphs[0]
|
p = footer.paragraphs[0]
|
||||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
p.paragraph_format.space_after = Pt(0)
|
p.paragraph_format.space_after = Pt(0)
|
||||||
format_run(p.add_run("Version 2.2.3 | Stand 10.08.2026 | Seite "), size=7.5, color=MUTED)
|
format_run(p.add_run("Version 2.2.4 | Stand 10.08.2026 | Seite "), size=7.5, color=MUTED)
|
||||||
add_page_field(p)
|
add_page_field(p)
|
||||||
|
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ def build_document():
|
|||||||
core.title = "BizTalk Checkmk Pulse – Architektur- und Lösungsüberblick"
|
core.title = "BizTalk Checkmk Pulse – Architektur- und Lösungsüberblick"
|
||||||
core.subject = "Gesamtarchitektur, Funktionsumfang und Datenaustausch"
|
core.subject = "Gesamtarchitektur, Funktionsumfang und Datenaustausch"
|
||||||
core.author = "BEW"
|
core.author = "BEW"
|
||||||
core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.3"
|
core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.4"
|
||||||
|
|
||||||
# Cover
|
# Cover
|
||||||
banner = document.add_table(rows=1, cols=1)
|
banner = document.add_table(rows=1, cols=1)
|
||||||
@@ -377,7 +377,7 @@ def build_document():
|
|||||||
document,
|
document,
|
||||||
("Dokumentmerkmal", "Angabe"),
|
("Dokumentmerkmal", "Angabe"),
|
||||||
(
|
(
|
||||||
("Lösungsstand", "BizTalk Checkmk Pulse 2.2.3"),
|
("Lösungsstand", "BizTalk Checkmk Pulse 2.2.4"),
|
||||||
("Zielplattform", "Microsoft BizTalk Server 2020 · Windows Server 2019 · Checkmk 2.4"),
|
("Zielplattform", "Microsoft BizTalk Server 2020 · Windows Server 2019 · Checkmk 2.4"),
|
||||||
("Dokumentstand", "10. August 2026"),
|
("Dokumentstand", "10. August 2026"),
|
||||||
("Dokumentzweck", "Architektur- und Leistungsüberblick für technische Stakeholder"),
|
("Dokumentzweck", "Architektur- und Leistungsüberblick für technische Stakeholder"),
|
||||||
@@ -544,6 +544,7 @@ def build_document():
|
|||||||
add_bullet(document, "Erwartet inaktive Receive Locations und Send Ports können exakt allowlisted werden und bleiben als Messwert sichtbar.")
|
add_bullet(document, "Erwartet inaktive Receive Locations und Send Ports können exakt allowlisted werden und bleiben als Messwert sichtbar.")
|
||||||
add_bullet(document, "Endpoint-Prüfung standardmäßig mit maximal 100 Zielen, 16 parallelen Probes und 3 Sekunden Timeout je Ziel.")
|
add_bullet(document, "Endpoint-Prüfung standardmäßig mit maximal 100 Zielen, 16 parallelen Probes und 3 Sekunden Timeout je Ziel.")
|
||||||
add_bullet(document, "Der Installer vergleicht bei Updates die exakten Checkmk-Servicenamen und stoppt unbestätigte Änderungen, bevor Task oder Programmdateien angefasst werden.")
|
add_bullet(document, "Der Installer vergleicht bei Updates die exakten Checkmk-Servicenamen und stoppt unbestätigte Änderungen, bevor Task oder Programmdateien angefasst werden.")
|
||||||
|
add_bullet(document, "Nach der Umschaltung erzwingt das Setup einen frischen Katalogabgleich und akzeptiert die Installation erst nach einem erfolgreichen, identitäts- und integritätsgeprüften Providerlauf ohne UNKNOWN in den neun stabilen Services.")
|
||||||
|
|
||||||
add_heading(document, "Sicherheitsmodell", level=2)
|
add_heading(document, "Sicherheitsmodell", level=2)
|
||||||
add_table(
|
add_table(
|
||||||
@@ -572,7 +573,7 @@ def build_document():
|
|||||||
add_callout(document, "Kernaussage: ", "Ein privilegierter Read-only-Collector sammelt, ein unprivilegierter Consumer validiert und übergibt – Checkmk erhält neun stabile Services statt direkten Zugriff auf BizTalk oder SQL.", fill=LIGHT_GREEN, border=GREEN)
|
add_callout(document, "Kernaussage: ", "Ein privilegierter Read-only-Collector sammelt, ein unprivilegierter Consumer validiert und übergibt – Checkmk erhält neun stabile Services statt direkten Zugriff auf BizTalk oder SQL.", fill=LIGHT_GREEN, border=GREEN)
|
||||||
add_rich_paragraph(
|
add_rich_paragraph(
|
||||||
document,
|
document,
|
||||||
(("Dokumentbasis: Implementierung und Projektdokumentation von BizTalk Checkmk Pulse 2.2.3 sowie Checkmk User Guide „Local checks“, abgerufen am 10. August 2026.", {"size": 7.8, "color": MUTED}),),
|
(("Dokumentbasis: Implementierung und Projektdokumentation von BizTalk Checkmk Pulse 2.2.4 sowie Checkmk User Guide „Local checks“, abgerufen am 10. August 2026.", {"size": 7.8, "color": MUTED}),),
|
||||||
before=8,
|
before=8,
|
||||||
after=0,
|
after=0,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
namespace BizTalkCheckmkPulse.Setup
|
namespace BizTalkCheckmkPulse.Setup
|
||||||
@@ -14,6 +15,7 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
internal sealed class InstallerEngine
|
internal sealed class InstallerEngine
|
||||||
{
|
{
|
||||||
internal const string TaskName = "BizTalk Checkmk Pulse Provider";
|
internal const string TaskName = "BizTalk Checkmk Pulse Provider";
|
||||||
|
private static readonly TimeSpan PostInstallValidationTimeout = TimeSpan.FromMinutes(4);
|
||||||
private readonly string packageDirectory;
|
private readonly string packageDirectory;
|
||||||
private readonly string installDirectory;
|
private readonly string installDirectory;
|
||||||
private readonly string runtimeDirectory;
|
private readonly string runtimeDirectory;
|
||||||
@@ -54,7 +56,8 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
var targetWrapper = Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd");
|
var targetWrapper = Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd");
|
||||||
var previousWrapper = File.Exists(targetWrapper) ? File.ReadAllBytes(targetWrapper) : null;
|
var previousWrapper = File.Exists(targetWrapper) ? File.ReadAllBytes(targetWrapper) : null;
|
||||||
var hadExistingInstallation = Directory.Exists(installDirectory);
|
var hadExistingInstallation = Directory.Exists(installDirectory);
|
||||||
var taskRemoved = false;
|
var hadExistingExecutable = File.Exists(targetExe);
|
||||||
|
var mutationStarted = false;
|
||||||
var backupCreated = false;
|
var backupCreated = false;
|
||||||
var filesActivated = false;
|
var filesActivated = false;
|
||||||
var scheduler = new TaskSchedulerService();
|
var scheduler = new TaskSchedulerService();
|
||||||
@@ -94,8 +97,8 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
|
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
|
||||||
|
mutationStarted = true;
|
||||||
scheduler.DeleteIfExists(TaskName);
|
scheduler.DeleteIfExists(TaskName);
|
||||||
taskRemoved = true;
|
|
||||||
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
|
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
|
||||||
|
|
||||||
if (hadExistingInstallation)
|
if (hadExistingInstallation)
|
||||||
@@ -129,21 +132,45 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
RunSelfTest(targetExe);
|
RunSelfTest(targetExe);
|
||||||
report("Installierter Self-Test erfolgreich: neun Checkmk-Services.");
|
report("Installierter Self-Test erfolgreich: neun Checkmk-Services.");
|
||||||
|
|
||||||
scheduler.RegisterAndStart(
|
var runtimeValidationStartedUtc = DateTime.UtcNow;
|
||||||
|
scheduler.RegisterValidationAndStart(
|
||||||
TaskName,
|
TaskName,
|
||||||
targetExe,
|
targetExe,
|
||||||
installDirectory,
|
installDirectory,
|
||||||
account,
|
account,
|
||||||
isGmsa ? null : password,
|
isGmsa ? null : password,
|
||||||
isGmsa);
|
isGmsa);
|
||||||
taskRemoved = false;
|
report("Einmaliger Provider-Abnahmelauf mit erzwungenem Endpoint-Katalogabgleich gestartet.");
|
||||||
report("Scheduled Task registriert und einmalig gestartet: " + TaskName);
|
|
||||||
|
var completedRun = scheduler.WaitForSuccessfulRun(
|
||||||
|
TaskName,
|
||||||
|
runtimeValidationStartedUtc,
|
||||||
|
PostInstallValidationTimeout,
|
||||||
|
report);
|
||||||
|
report("Provider-Abnahmelauf erfolgreich: LastTaskResult=" + completedRun.ExitCode + ".");
|
||||||
|
|
||||||
|
var runtimeValidation = RunRuntimeValidation(
|
||||||
|
targetExe,
|
||||||
|
runtimeValidationStartedUtc,
|
||||||
|
account);
|
||||||
|
report("Post-Install-Runtime-Abnahme erfolgreich: " + runtimeValidation);
|
||||||
|
|
||||||
|
scheduler.RegisterRecurring(
|
||||||
|
TaskName,
|
||||||
|
targetExe,
|
||||||
|
installDirectory,
|
||||||
|
account,
|
||||||
|
isGmsa ? null : password,
|
||||||
|
isGmsa);
|
||||||
|
report("Validierter minuetlicher Scheduled Task registriert: " + TaskName);
|
||||||
|
|
||||||
TryDeleteDirectory(backupDirectory, report);
|
TryDeleteDirectory(backupDirectory, report);
|
||||||
}
|
}
|
||||||
catch (Exception installException)
|
catch (Exception installException)
|
||||||
{
|
{
|
||||||
var rollbackFailures = new List<string>();
|
var rollbackFailures = new List<string>();
|
||||||
|
if (mutationStarted)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
scheduler.DeleteIfExists(TaskName);
|
scheduler.DeleteIfExists(TaskName);
|
||||||
@@ -152,6 +179,7 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
{
|
{
|
||||||
rollbackFailures.Add("Task stoppen: " + ex.Message);
|
rollbackFailures.Add("Task stoppen: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (filesActivated || backupCreated)
|
if (filesActivated || backupCreated)
|
||||||
{
|
{
|
||||||
@@ -167,6 +195,8 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mutationStarted)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RestoreWrapper(targetWrapper, previousWrapper);
|
RestoreWrapper(targetWrapper, previousWrapper);
|
||||||
@@ -175,19 +205,26 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
{
|
{
|
||||||
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
|
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (taskRemoved && hadExistingInstallation && File.Exists(targetExe))
|
if (mutationStarted && hadExistingExecutable && File.Exists(targetExe))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
scheduler.RegisterAndStart(
|
var rollbackRunStartedUtc = DateTime.UtcNow;
|
||||||
|
scheduler.RegisterRecurringAndStart(
|
||||||
TaskName,
|
TaskName,
|
||||||
targetExe,
|
targetExe,
|
||||||
installDirectory,
|
installDirectory,
|
||||||
account,
|
account,
|
||||||
isGmsa ? null : password,
|
isGmsa ? null : password,
|
||||||
isGmsa);
|
isGmsa);
|
||||||
report("Scheduled Task fuer die vorherige Version wiederhergestellt.");
|
scheduler.WaitForSuccessfulRun(
|
||||||
|
TaskName,
|
||||||
|
rollbackRunStartedUtc,
|
||||||
|
PostInstallValidationTimeout,
|
||||||
|
report);
|
||||||
|
report("Scheduled Task und frischer Snapshot der vorherigen Version wiederhergestellt.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -445,6 +482,45 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string RunRuntimeValidation(
|
||||||
|
string executable,
|
||||||
|
DateTime notBeforeUtc,
|
||||||
|
string expectedIdentity)
|
||||||
|
{
|
||||||
|
var identityBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(expectedIdentity));
|
||||||
|
var arguments = "--validate-runtime --validation-not-before-utc "
|
||||||
|
+ notBeforeUtc.ToUniversalTime().ToString("o")
|
||||||
|
+ " --expected-identity-base64 " + identityBase64;
|
||||||
|
var start = new ProcessStartInfo(executable, arguments)
|
||||||
|
{
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true
|
||||||
|
};
|
||||||
|
using (var process = Process.Start(start))
|
||||||
|
{
|
||||||
|
if (process == null) throw new InvalidOperationException("Runtime-Abnahme konnte nicht gestartet werden.");
|
||||||
|
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||||
|
var error = process.StandardError.ReadToEnd().Trim();
|
||||||
|
if (!process.WaitForExit(30000))
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
throw new TimeoutException("Runtime-Abnahme hat das Zeitlimit ueberschritten.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.ExitCode != 0
|
||||||
|
|| !output.StartsWith("RUNTIME_VALIDATION_V1 ", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Runtime-Abnahme fehlgeschlagen. Exitcode=" + process.ExitCode
|
||||||
|
+ ". " + (error.Length == 0 ? output : error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string ExtractServiceName(string line)
|
private static string ExtractServiceName(string line)
|
||||||
{
|
{
|
||||||
var firstQuote = line.IndexOf('"');
|
var firstQuote = line.IndexOf('"');
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ using System.Runtime.CompilerServices;
|
|||||||
[assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")]
|
[assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")]
|
||||||
[assembly: AssemblyCompany("BEW")]
|
[assembly: AssemblyCompany("BEW")]
|
||||||
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
||||||
[assembly: AssemblyVersion("2.2.3.0")]
|
[assembly: AssemblyVersion("2.2.4.0")]
|
||||||
[assembly: AssemblyFileVersion("2.2.3.0")]
|
[assembly: AssemblyFileVersion("2.2.4.0")]
|
||||||
|
|||||||
@@ -13,18 +13,64 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
private const int TaskInstancesIgnoreNew = 2;
|
private const int TaskInstancesIgnoreNew = 2;
|
||||||
private const int TaskStateRunning = 4;
|
private const int TaskStateRunning = 4;
|
||||||
|
|
||||||
public void RegisterAndStart(
|
public void RegisterValidationAndStart(
|
||||||
string taskName,
|
string taskName,
|
||||||
string executable,
|
string executable,
|
||||||
string workingDirectory,
|
string workingDirectory,
|
||||||
string account,
|
string account,
|
||||||
string password,
|
string password,
|
||||||
bool isGmsa)
|
bool isGmsa)
|
||||||
|
{
|
||||||
|
Register(
|
||||||
|
taskName,
|
||||||
|
executable,
|
||||||
|
workingDirectory,
|
||||||
|
account,
|
||||||
|
password,
|
||||||
|
isGmsa,
|
||||||
|
"--collect --force-endpoint-refresh",
|
||||||
|
false,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterRecurring(
|
||||||
|
string taskName,
|
||||||
|
string executable,
|
||||||
|
string workingDirectory,
|
||||||
|
string account,
|
||||||
|
string password,
|
||||||
|
bool isGmsa)
|
||||||
|
{
|
||||||
|
Register(taskName, executable, workingDirectory, account, password, isGmsa, "--collect", true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterRecurringAndStart(
|
||||||
|
string taskName,
|
||||||
|
string executable,
|
||||||
|
string workingDirectory,
|
||||||
|
string account,
|
||||||
|
string password,
|
||||||
|
bool isGmsa)
|
||||||
|
{
|
||||||
|
Register(taskName, executable, workingDirectory, account, password, isGmsa, "--collect", true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Register(
|
||||||
|
string taskName,
|
||||||
|
string executable,
|
||||||
|
string workingDirectory,
|
||||||
|
string account,
|
||||||
|
string password,
|
||||||
|
bool isGmsa,
|
||||||
|
string arguments,
|
||||||
|
bool enableRecurringTrigger,
|
||||||
|
bool startImmediately)
|
||||||
{
|
{
|
||||||
dynamic service = null;
|
dynamic service = null;
|
||||||
dynamic root = null;
|
dynamic root = null;
|
||||||
dynamic definition = null;
|
dynamic definition = null;
|
||||||
dynamic registeredTask = null;
|
dynamic registeredTask = null;
|
||||||
|
dynamic runningTask = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var schedulerType = Type.GetTypeFromProgID("Schedule.Service", true);
|
var schedulerType = Type.GetTypeFromProgID("Schedule.Service", true);
|
||||||
@@ -52,12 +98,12 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
|
|
||||||
dynamic trigger = definition.Triggers.Create(TaskTriggerTime);
|
dynamic trigger = definition.Triggers.Create(TaskTriggerTime);
|
||||||
trigger.StartBoundary = DateTime.Now.AddMinutes(1).ToString("s");
|
trigger.StartBoundary = DateTime.Now.AddMinutes(1).ToString("s");
|
||||||
trigger.Enabled = true;
|
trigger.Enabled = enableRecurringTrigger;
|
||||||
trigger.Repetition.Interval = "PT1M";
|
trigger.Repetition.Interval = "PT1M";
|
||||||
|
|
||||||
dynamic action = definition.Actions.Create(TaskActionExecute);
|
dynamic action = definition.Actions.Create(TaskActionExecute);
|
||||||
action.Path = executable;
|
action.Path = executable;
|
||||||
action.Arguments = "--collect";
|
action.Arguments = arguments;
|
||||||
action.WorkingDirectory = workingDirectory;
|
action.WorkingDirectory = workingDirectory;
|
||||||
|
|
||||||
registeredTask = root.RegisterTaskDefinition(
|
registeredTask = root.RegisterTaskDefinition(
|
||||||
@@ -68,10 +114,14 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
isGmsa ? null : password,
|
isGmsa ? null : password,
|
||||||
TaskLogonPassword,
|
TaskLogonPassword,
|
||||||
null);
|
null);
|
||||||
registeredTask.Run(null);
|
if (startImmediately)
|
||||||
|
{
|
||||||
|
runningTask = registeredTask.Run(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
Release(runningTask);
|
||||||
Release(registeredTask);
|
Release(registeredTask);
|
||||||
Release(definition);
|
Release(definition);
|
||||||
Release(root);
|
Release(root);
|
||||||
@@ -79,6 +129,81 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TaskRunResult WaitForSuccessfulRun(
|
||||||
|
string taskName,
|
||||||
|
DateTime notBeforeUtc,
|
||||||
|
TimeSpan timeout,
|
||||||
|
Action<string> report)
|
||||||
|
{
|
||||||
|
dynamic service = null;
|
||||||
|
dynamic root = null;
|
||||||
|
dynamic task = null;
|
||||||
|
var startedUtc = DateTime.UtcNow;
|
||||||
|
var nextReportUtc = startedUtc.AddSeconds(15);
|
||||||
|
var lastState = -1;
|
||||||
|
var lastResult = int.MinValue;
|
||||||
|
DateTime? lastRunUtc = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var schedulerType = Type.GetTypeFromProgID("Schedule.Service", true);
|
||||||
|
service = Activator.CreateInstance(schedulerType);
|
||||||
|
service.Connect();
|
||||||
|
root = service.GetFolder("\\");
|
||||||
|
task = root.GetTask(taskName);
|
||||||
|
|
||||||
|
while (DateTime.UtcNow - startedUtc < timeout)
|
||||||
|
{
|
||||||
|
lastState = (int)task.State;
|
||||||
|
lastResult = (int)task.LastTaskResult;
|
||||||
|
var lastRun = (DateTime)task.LastRunTime;
|
||||||
|
lastRunUtc = lastRun.Year <= 1900
|
||||||
|
? (DateTime?)null
|
||||||
|
: DateTime.SpecifyKind(lastRun, DateTimeKind.Local).ToUniversalTime();
|
||||||
|
|
||||||
|
if (IsSuccessfulCompletion(lastState, lastResult, lastRunUtc, notBeforeUtc))
|
||||||
|
{
|
||||||
|
return new TaskRunResult(lastRunUtc.Value, lastResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (report != null && DateTime.UtcNow >= nextReportUtc)
|
||||||
|
{
|
||||||
|
report(
|
||||||
|
"Warte auf erfolgreichen Providerlauf: state=" + lastState
|
||||||
|
+ ", last_result=" + lastResult
|
||||||
|
+ ", last_run=" + (lastRunUtc.HasValue ? lastRunUtc.Value.ToString("o") : "(noch keiner)") + ".");
|
||||||
|
nextReportUtc = DateTime.UtcNow.AddSeconds(15);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException(
|
||||||
|
"Providerlauf wurde innerhalb von " + Math.Ceiling(timeout.TotalSeconds)
|
||||||
|
+ " Sekunden nicht erfolgreich abgeschlossen. state=" + lastState
|
||||||
|
+ ", last_result=" + lastResult
|
||||||
|
+ ", last_run=" + (lastRunUtc.HasValue ? lastRunUtc.Value.ToString("o") : "(noch keiner)") + ".");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Release(task);
|
||||||
|
Release(root);
|
||||||
|
Release(service);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsSuccessfulCompletion(
|
||||||
|
int taskState,
|
||||||
|
int lastTaskResult,
|
||||||
|
DateTime? lastRunUtc,
|
||||||
|
DateTime notBeforeUtc)
|
||||||
|
{
|
||||||
|
return taskState != TaskStateRunning
|
||||||
|
&& lastTaskResult == 0
|
||||||
|
&& lastRunUtc.HasValue
|
||||||
|
// Task Scheduler kann LastRunTime mit geringerer Aufloesung als DateTime.UtcNow liefern.
|
||||||
|
&& lastRunUtc.Value >= notBeforeUtc.ToUniversalTime().AddSeconds(-2);
|
||||||
|
}
|
||||||
|
|
||||||
public void DeleteIfExists(string taskName)
|
public void DeleteIfExists(string taskName)
|
||||||
{
|
{
|
||||||
dynamic service = null;
|
dynamic service = null;
|
||||||
@@ -131,4 +256,16 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
if (value != null && Marshal.IsComObject(value)) Marshal.FinalReleaseComObject(value);
|
if (value != null && Marshal.IsComObject(value)) Marshal.FinalReleaseComObject(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal sealed class TaskRunResult
|
||||||
|
{
|
||||||
|
public TaskRunResult(DateTime lastRunUtc, int exitCode)
|
||||||
|
{
|
||||||
|
LastRunUtc = lastRunUtc;
|
||||||
|
ExitCode = exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime LastRunUtc { get; private set; }
|
||||||
|
public int ExitCode { get; private set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
<add key="Server" value="." />
|
<add key="Server" value="." />
|
||||||
<add key="ServicePrefix" value="BizTalk" />
|
<add key="ServicePrefix" value="BizTalk" />
|
||||||
<add key="EnvironmentName" value="" />
|
<add key="EnvironmentName" value="" />
|
||||||
|
<!-- Stabiler Checkmk-Servicevertrag; nur mit geplanter Service Discovery aktivieren. -->
|
||||||
|
<add key="IncludeEnvironmentInServiceName" value="false" />
|
||||||
|
|
||||||
<!-- Provider/Consumer-Datei. Der Scheduled Task schreibt, LocalSystem liest. -->
|
<!-- Provider/Consumer-Datei. Der Scheduled Task schreibt, LocalSystem liest. -->
|
||||||
<add key="SnapshotPath" value="%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot" />
|
<add key="SnapshotPath" value="%ProgramData%\BizTalkCheckmkPulse\data\biztalk-checkmk-pulse.snapshot" />
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
<Compile Include="MonitoringOptions.cs" />
|
<Compile Include="MonitoringOptions.cs" />
|
||||||
<Compile Include="Models.cs" />
|
<Compile Include="Models.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="RuntimeValidator.cs" />
|
||||||
<Compile Include="SqlConnectivityProbe.cs" />
|
<Compile Include="SqlConnectivityProbe.cs" />
|
||||||
<Compile Include="SnapshotStore.cs" />
|
<Compile Include="SnapshotStore.cs" />
|
||||||
<Compile Include="WmiBizTalkProbe.cs" />
|
<Compile Include="WmiBizTalkProbe.cs" />
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
state.RefreshRequired = catalog == null
|
state.RefreshRequired = _options.ForceEndpointCatalogRefresh
|
||||||
|
|| catalog == null
|
||||||
|| catalog.SynchronizedUtc > now.AddMinutes(5)
|
|| catalog.SynchronizedUtc > now.AddMinutes(5)
|
||||||
|| now - catalog.SynchronizedUtc >= TimeSpan.FromHours(_options.EndpointDiscoveryIntervalHours);
|
|| now - catalog.SynchronizedUtc >= TimeSpan.FromHours(_options.EndpointDiscoveryIntervalHours);
|
||||||
if (state.RefreshRequired)
|
if (state.RefreshRequired)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Configuration;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace BizTalkCheckmkPulse
|
namespace BizTalkCheckmkPulse
|
||||||
{
|
{
|
||||||
@@ -49,6 +50,10 @@ namespace BizTalkCheckmkPulse
|
|||||||
public int EndpointMaxCount { get; set; }
|
public int EndpointMaxCount { get; set; }
|
||||||
public bool Collect { get; set; }
|
public bool Collect { get; set; }
|
||||||
public bool SelfTest { get; set; }
|
public bool SelfTest { get; set; }
|
||||||
|
public bool ForceEndpointCatalogRefresh { get; set; }
|
||||||
|
public bool RuntimeValidation { get; set; }
|
||||||
|
public DateTime? RuntimeValidationNotBeforeUtc { get; set; }
|
||||||
|
public string ExpectedRuntimeIdentity { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Setzt konservative Standardwerte fuer BizTalk Server 2020.
|
/// Setzt konservative Standardwerte fuer BizTalk Server 2020.
|
||||||
@@ -91,6 +96,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
EndpointProbeTimeoutMilliseconds = 3000;
|
EndpointProbeTimeoutMilliseconds = 3000;
|
||||||
EndpointProbeMaxConcurrency = 16;
|
EndpointProbeMaxConcurrency = 16;
|
||||||
EndpointMaxCount = 100;
|
EndpointMaxCount = 100;
|
||||||
|
ExpectedRuntimeIdentity = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -182,6 +188,33 @@ namespace BizTalkCheckmkPulse
|
|||||||
{
|
{
|
||||||
options.Collect = true;
|
options.Collect = true;
|
||||||
}
|
}
|
||||||
|
else if (EqualsAny(arg, "--force-endpoint-refresh", "/force-endpoint-refresh"))
|
||||||
|
{
|
||||||
|
options.ForceEndpointCatalogRefresh = true;
|
||||||
|
}
|
||||||
|
else if (EqualsAny(arg, "--validate-runtime", "/validate-runtime"))
|
||||||
|
{
|
||||||
|
options.RuntimeValidation = true;
|
||||||
|
}
|
||||||
|
else if (EqualsAny(arg, "--validation-not-before-utc", "/validation-not-before-utc") && i + 1 < args.Length)
|
||||||
|
{
|
||||||
|
DateTime value;
|
||||||
|
if (!DateTime.TryParseExact(
|
||||||
|
args[++i],
|
||||||
|
"o",
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
|
||||||
|
out value))
|
||||||
|
{
|
||||||
|
throw new ConfigurationErrorsException("Runtime validation timestamp must use the round-trip UTC format.");
|
||||||
|
}
|
||||||
|
options.RuntimeValidationNotBeforeUtc = value;
|
||||||
|
}
|
||||||
|
else if (EqualsAny(arg, "--expected-identity-base64", "/expected-identity-base64") && i + 1 < args.Length)
|
||||||
|
{
|
||||||
|
options.ExpectedRuntimeIdentity = new UTF8Encoding(false, true)
|
||||||
|
.GetString(Convert.FromBase64String(args[++i]));
|
||||||
|
}
|
||||||
else if (EqualsAny(arg, "--consume", "/consume"))
|
else if (EqualsAny(arg, "--consume", "/consume"))
|
||||||
{
|
{
|
||||||
options.Collect = false;
|
options.Collect = false;
|
||||||
@@ -214,6 +247,18 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void Validate(MonitoringOptions options)
|
private static void Validate(MonitoringOptions options)
|
||||||
{
|
{
|
||||||
|
if (options.ForceEndpointCatalogRefresh && !options.Collect)
|
||||||
|
{
|
||||||
|
throw new ConfigurationErrorsException("ForceEndpointCatalogRefresh is valid only in provider mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.RuntimeValidation
|
||||||
|
&& (!options.RuntimeValidationNotBeforeUtc.HasValue
|
||||||
|
|| string.IsNullOrWhiteSpace(options.ExpectedRuntimeIdentity)))
|
||||||
|
{
|
||||||
|
throw new ConfigurationErrorsException("Runtime validation requires a start timestamp and expected identity.");
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(options.SnapshotPath) || !Path.IsPathRooted(options.SnapshotPath))
|
if (string.IsNullOrWhiteSpace(options.SnapshotPath) || !Path.IsPathRooted(options.SnapshotPath))
|
||||||
{
|
{
|
||||||
throw new ConfigurationErrorsException("SnapshotPath must be an absolute path.");
|
throw new ConfigurationErrorsException("SnapshotPath must be an absolute path.");
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ namespace BizTalkCheckmkPulse
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.RuntimeValidation)
|
||||||
|
{
|
||||||
|
return RunRuntimeValidation(options, formatter);
|
||||||
|
}
|
||||||
|
|
||||||
return options.Collect
|
return options.Collect
|
||||||
? RunProvider(options, formatter)
|
? RunProvider(options, formatter)
|
||||||
: RunConsumer(options, formatter);
|
: RunConsumer(options, formatter);
|
||||||
@@ -51,7 +56,14 @@ namespace BizTalkCheckmkPulse
|
|||||||
Console.WriteLine(line);
|
Console.WriteLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
return options != null && options.Collect ? 1 : 0;
|
var strictFailureMode = options != null
|
||||||
|
? options.Collect || options.RuntimeValidation
|
||||||
|
: (args ?? new string[0]).Any(x =>
|
||||||
|
string.Equals(x, "--collect", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(x, "/collect", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(x, "--validate-runtime", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(x, "/validate-runtime", StringComparison.OrdinalIgnoreCase));
|
||||||
|
return strictFailureMode ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,13 +111,9 @@ namespace BizTalkCheckmkPulse
|
|||||||
+ " send_ports_not_started="
|
+ " send_ports_not_started="
|
||||||
+ result.SendPorts.Count(x => x.Status != 3)
|
+ result.SendPorts.Count(x => x.Status != 3)
|
||||||
+ " endpoint_candidates_active="
|
+ " endpoint_candidates_active="
|
||||||
+ result.EndpointConnectivity.ActiveCandidates
|
+ result.EndpointCandidates.Count(x => x.Active)
|
||||||
+ " endpoints_active="
|
+ " endpoints_active="
|
||||||
+ result.EndpointConnectivity.Active
|
+ result.EndpointConnectivity.Active
|
||||||
+ " endpoints_unsupported="
|
|
||||||
+ result.EndpointConnectivity.UnsupportedActive
|
|
||||||
+ " endpoints_excluded="
|
|
||||||
+ result.EndpointConnectivity.ExcludedActive
|
|
||||||
+ " endpoints_failed="
|
+ " endpoints_failed="
|
||||||
+ result.EndpointConnectivity.Results.Count(x => !x.Available)
|
+ result.EndpointConnectivity.Results.Count(x => !x.Available)
|
||||||
+ " endpoints_unresolved="
|
+ " endpoints_unresolved="
|
||||||
@@ -137,6 +145,29 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int RunRuntimeValidation(MonitoringOptions options, CheckmkLocalFormatter formatter)
|
||||||
|
{
|
||||||
|
var validation = RuntimeValidator.Validate(
|
||||||
|
options,
|
||||||
|
formatter,
|
||||||
|
options.RuntimeValidationNotBeforeUtc.Value,
|
||||||
|
options.ExpectedRuntimeIdentity,
|
||||||
|
DateTime.UtcNow);
|
||||||
|
if (!validation.IsSuccess)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Runtime validation failed: " + validation.Error);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(
|
||||||
|
"RUNTIME_VALIDATION_V1 generatedUtc=" + validation.GeneratedUtc.ToString("o")
|
||||||
|
+ " catalogUtc=" + (validation.CatalogSynchronizedUtc.HasValue
|
||||||
|
? validation.CatalogSynchronizedUtc.Value.ToString("o")
|
||||||
|
: "disabled")
|
||||||
|
+ " stableServices=" + validation.StableServiceCount);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnsureProviderIdentity()
|
private static void EnsureProviderIdentity()
|
||||||
{
|
{
|
||||||
using (var identity = WindowsIdentity.GetCurrent())
|
using (var identity = WindowsIdentity.GetCurrent())
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ using System.Reflection;
|
|||||||
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
|
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
|
||||||
[assembly: AssemblyCompany("BEW")]
|
[assembly: AssemblyCompany("BEW")]
|
||||||
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
||||||
[assembly: AssemblyVersion("2.2.3.0")]
|
[assembly: AssemblyVersion("2.2.4.0")]
|
||||||
[assembly: AssemblyFileVersion("2.2.3.0")]
|
[assembly: AssemblyFileVersion("2.2.4.0")]
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace BizTalkCheckmkPulse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Validiert die vom ersten Providerlauf erzeugten Runtime-Artefakte mit denselben
|
||||||
|
/// Parsern und Grenzen, die auch der laufende Consumer verwendet.
|
||||||
|
/// </summary>
|
||||||
|
internal static class RuntimeValidator
|
||||||
|
{
|
||||||
|
public static RuntimeValidationResult Validate(
|
||||||
|
MonitoringOptions options,
|
||||||
|
CheckmkLocalFormatter formatter,
|
||||||
|
DateTime notBeforeUtc,
|
||||||
|
string expectedIdentity,
|
||||||
|
DateTime utcNow)
|
||||||
|
{
|
||||||
|
if (options == null) throw new ArgumentNullException("options");
|
||||||
|
if (formatter == null) throw new ArgumentNullException("formatter");
|
||||||
|
|
||||||
|
var snapshot = new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
|
||||||
|
.Read(utcNow, TimeSpan.FromSeconds(options.SnapshotMaxAgeSeconds));
|
||||||
|
if (!snapshot.IsSuccess)
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed("Snapshot ist ungueltig: " + snapshot.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
var boundary = notBeforeUtc.ToUniversalTime();
|
||||||
|
if (snapshot.GeneratedUtc < boundary)
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed(
|
||||||
|
"Snapshot stammt nicht aus dem gestarteten Providerlauf. generatedUtc="
|
||||||
|
+ snapshot.GeneratedUtc.ToString("o") + ", requiredUtc=" + boundary.ToString("o") + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(snapshot.Identity, expectedIdentity, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed(
|
||||||
|
"Snapshot wurde von einer unerwarteten Identitaet erzeugt. expected="
|
||||||
|
+ expectedIdentity + ", actual=" + (snapshot.Identity ?? "(leer)") + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
string serviceError;
|
||||||
|
var expectedServices = formatter.FormatSelfTest().Select(ExtractServiceName).ToArray();
|
||||||
|
if (!ContainsExactStableContract(snapshot.Lines, expectedServices, out serviceError))
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed(serviceError);
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime? catalogUtc = null;
|
||||||
|
if (options.ProbeEndpointConnectivity)
|
||||||
|
{
|
||||||
|
EndpointCatalog catalog;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
catalog = new EndpointCatalogStore(
|
||||||
|
options.EndpointCatalogPath,
|
||||||
|
options.EndpointCatalogMaxBytes,
|
||||||
|
options.EndpointCatalogMaxEntries)
|
||||||
|
.Read(options.EnvironmentName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed(
|
||||||
|
"Endpoint-Katalog ist ungueltig: " + ex.GetType().Name + ": " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catalog.SynchronizedUtc < boundary)
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed(
|
||||||
|
"Endpoint-Katalog wurde beim Installationslauf nicht aktualisiert. synchronizedUtc="
|
||||||
|
+ catalog.SynchronizedUtc.ToString("o") + ", requiredUtc=" + boundary.ToString("o") + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catalog.SynchronizedUtc > utcNow.ToUniversalTime().AddMinutes(5))
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed("Endpoint-Katalog-Zeitstempel liegt unplausibel in der Zukunft.");
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogUtc = catalog.SynchronizedUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RuntimeValidationResult.Success(
|
||||||
|
snapshot.GeneratedUtc,
|
||||||
|
catalogUtc,
|
||||||
|
expectedServices.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool ContainsExactStableContract(
|
||||||
|
IEnumerable<string> lines,
|
||||||
|
IEnumerable<string> expectedServices,
|
||||||
|
out string error)
|
||||||
|
{
|
||||||
|
string[] actual;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
actual = (lines ?? Enumerable.Empty<string>()).Select(ExtractServiceName).ToArray();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
error = "Snapshot-Servicevertrag kann nicht gelesen werden: " + ex.Message;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actual.Distinct(StringComparer.Ordinal).Count() != actual.Length)
|
||||||
|
{
|
||||||
|
error = "Snapshot enthaelt doppelte Checkmk-Servicenamen.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actualSet = new HashSet<string>(actual, StringComparer.Ordinal);
|
||||||
|
var expected = (expectedServices ?? Enumerable.Empty<string>()).ToArray();
|
||||||
|
var missing = expected
|
||||||
|
.Where(x => !actualSet.Contains(x))
|
||||||
|
.OrderBy(x => x, StringComparer.Ordinal)
|
||||||
|
.ToArray();
|
||||||
|
if (missing.Length != 0)
|
||||||
|
{
|
||||||
|
error = "Snapshot enthaelt nicht alle stabilen Checkmk-Services. Fehlend=["
|
||||||
|
+ string.Join(", ", missing) + "].";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var unknown = (lines ?? Enumerable.Empty<string>())
|
||||||
|
.Where(x => x != null && x.StartsWith("3 ", StringComparison.Ordinal))
|
||||||
|
.Select(ExtractServiceName)
|
||||||
|
.Where(x => expected.Contains(x, StringComparer.Ordinal))
|
||||||
|
.OrderBy(x => x, StringComparer.Ordinal)
|
||||||
|
.ToArray();
|
||||||
|
if (unknown.Length != 0)
|
||||||
|
{
|
||||||
|
error = "Snapshot enthaelt unzuverlaessige UNKNOWN-Zustaende in stabilen Services. UNKNOWN=["
|
||||||
|
+ string.Join(", ", unknown) + "].";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ExtractServiceName(string line)
|
||||||
|
{
|
||||||
|
var firstQuote = (line ?? string.Empty).IndexOf('"');
|
||||||
|
var secondQuote = firstQuote < 0 ? -1 : line.IndexOf('"', firstQuote + 1);
|
||||||
|
if (firstQuote < 0 || secondQuote <= firstQuote + 1)
|
||||||
|
throw new InvalidOperationException("Ungueltige Local-Check-Zeile.");
|
||||||
|
return line.Substring(firstQuote + 1, secondQuote - firstQuote - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class RuntimeValidationResult
|
||||||
|
{
|
||||||
|
private RuntimeValidationResult() { }
|
||||||
|
|
||||||
|
public bool IsSuccess { get; private set; }
|
||||||
|
public string Error { get; private set; }
|
||||||
|
public DateTime GeneratedUtc { get; private set; }
|
||||||
|
public DateTime? CatalogSynchronizedUtc { get; private set; }
|
||||||
|
public int StableServiceCount { get; private set; }
|
||||||
|
|
||||||
|
public static RuntimeValidationResult Success(DateTime generatedUtc, DateTime? catalogUtc, int serviceCount)
|
||||||
|
{
|
||||||
|
return new RuntimeValidationResult
|
||||||
|
{
|
||||||
|
IsSuccess = true,
|
||||||
|
GeneratedUtc = generatedUtc,
|
||||||
|
CatalogSynchronizedUtc = catalogUtc,
|
||||||
|
StableServiceCount = serviceCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RuntimeValidationResult Failed(string error)
|
||||||
|
{
|
||||||
|
return new RuntimeValidationResult { Error = error };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -114,7 +114,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
|
|
||||||
var expectedMachine = DecodeHeader(headerLines[2], "machineBase64=");
|
var expectedMachine = DecodeHeader(headerLines[2], "machineBase64=");
|
||||||
DecodeHeader(headerLines[3], "identityBase64=");
|
var identity = DecodeHeader(headerLines[3], "identityBase64=");
|
||||||
var expectedHash = ReadHeaderValue(headerLines[5], "payloadSha256=");
|
var expectedHash = ReadHeaderValue(headerLines[5], "payloadSha256=");
|
||||||
if (!string.Equals(expectedMachine, Environment.MachineName, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(expectedMachine, Environment.MachineName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -159,7 +159,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
+ "s.");
|
+ "s.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return SnapshotReadResult.Success(lines, generatedUtc);
|
return SnapshotReadResult.Success(lines, generatedUtc, identity);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
@@ -330,14 +330,16 @@ namespace BizTalkCheckmkPulse
|
|||||||
public string Error { get; private set; }
|
public string Error { get; private set; }
|
||||||
public IReadOnlyList<string> Lines { get; private set; }
|
public IReadOnlyList<string> Lines { get; private set; }
|
||||||
public DateTime GeneratedUtc { get; private set; }
|
public DateTime GeneratedUtc { get; private set; }
|
||||||
|
public string Identity { get; private set; }
|
||||||
|
|
||||||
public static SnapshotReadResult Success(IReadOnlyList<string> lines, DateTime generatedUtc)
|
public static SnapshotReadResult Success(IReadOnlyList<string> lines, DateTime generatedUtc, string identity)
|
||||||
{
|
{
|
||||||
return new SnapshotReadResult
|
return new SnapshotReadResult
|
||||||
{
|
{
|
||||||
IsSuccess = true,
|
IsSuccess = true,
|
||||||
Lines = lines,
|
Lines = lines,
|
||||||
GeneratedUtc = generatedUtc
|
GeneratedUtc = generatedUtc,
|
||||||
|
Identity = identity
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,14 +37,22 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
|
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
|
||||||
Run("EndpointOutputExplainsOnlyRealResolutionGaps", EndpointOutputExplainsOnlyRealResolutionGaps);
|
Run("EndpointOutputExplainsOnlyRealResolutionGaps", EndpointOutputExplainsOnlyRealResolutionGaps);
|
||||||
Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval);
|
Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval);
|
||||||
<<<<<<< HEAD
|
Run("ForcedEndpointRefreshRewritesFreshCatalog", ForcedEndpointRefreshRewritesFreshCatalog);
|
||||||
=======
|
|
||||||
Run("EnvironmentLabelDoesNotRenameServicesByDefault", EnvironmentLabelDoesNotRenameServicesByDefault);
|
Run("EnvironmentLabelDoesNotRenameServicesByDefault", EnvironmentLabelDoesNotRenameServicesByDefault);
|
||||||
Run("DefaultServiceContractIsExact", DefaultServiceContractIsExact);
|
Run("DefaultServiceContractIsExact", DefaultServiceContractIsExact);
|
||||||
Run("InstallerRejectsUnconfirmedServiceRename", InstallerRejectsUnconfirmedServiceRename);
|
Run("InstallerRejectsUnconfirmedServiceRename", InstallerRejectsUnconfirmedServiceRename);
|
||||||
Run("InstallerAllowsConfirmedServiceRename", InstallerAllowsConfirmedServiceRename);
|
Run("InstallerAllowsConfirmedServiceRename", InstallerAllowsConfirmedServiceRename);
|
||||||
>>>>>>> e694e4c (Guard Checkmk service renames during updates)
|
|
||||||
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
|
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
|
||||||
|
Run("RuntimeValidationAcceptsFreshConsistentArtifacts", RuntimeValidationAcceptsFreshConsistentArtifacts);
|
||||||
|
Run("RuntimeValidationRejectsPreInstallSnapshot", RuntimeValidationRejectsPreInstallSnapshot);
|
||||||
|
Run("RuntimeValidationRejectsWrongIdentity", RuntimeValidationRejectsWrongIdentity);
|
||||||
|
Run("RuntimeValidationRejectsStaleCatalog", RuntimeValidationRejectsStaleCatalog);
|
||||||
|
Run("RuntimeValidationRejectsChangedServiceContract", RuntimeValidationRejectsChangedServiceContract);
|
||||||
|
Run("RuntimeValidationRejectsUnknownStableService", RuntimeValidationRejectsUnknownStableService);
|
||||||
|
Run("RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog", RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog);
|
||||||
|
Run("ForceEndpointRefreshRequiresProviderMode", ForceEndpointRefreshRequiresProviderMode);
|
||||||
|
Run("RuntimeValidationArgumentsAreParsedStrictly", RuntimeValidationArgumentsAreParsedStrictly);
|
||||||
|
Run("TaskCompletionRequiresFreshSuccessfulRun", TaskCompletionRequiresFreshSuccessfulRun);
|
||||||
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
||||||
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
||||||
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
||||||
@@ -337,6 +345,7 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Assert(result.IsSuccess, "snapshot should be readable: " + result.Error);
|
Assert(result.IsSuccess, "snapshot should be readable: " + result.Error);
|
||||||
AssertEqual(lines.Length, result.Lines.Count, "snapshot line count");
|
AssertEqual(lines.Length, result.Lines.Count, "snapshot line count");
|
||||||
Assert(lines.SequenceEqual(result.Lines), "snapshot payload changed");
|
Assert(lines.SequenceEqual(result.Lines), "snapshot payload changed");
|
||||||
|
AssertEqual("DOMAIN\\collector$", result.Identity, "snapshot identity");
|
||||||
|
|
||||||
var replacement = lines.Select(x => x.Replace("Self test OK", "Replacement OK")).ToArray();
|
var replacement = lines.Select(x => x.Replace("Self test OK", "Replacement OK")).ToArray();
|
||||||
store.Write(replacement, generated.AddSeconds(30), "DOMAIN\\collector$");
|
store.Write(replacement, generated.AddSeconds(30), "DOMAIN\\collector$");
|
||||||
@@ -690,8 +699,51 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Assert(configuredMaximum <= 30000, "configured endpoint worst-case must leave headroom in the minute interval");
|
Assert(configuredMaximum <= 30000, "configured endpoint worst-case must leave headroom in the minute interval");
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
private static void ForcedEndpointRefreshRewritesFreshCatalog()
|
||||||
=======
|
{
|
||||||
|
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.ForcedCatalogTests." + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new MonitoringOptions
|
||||||
|
{
|
||||||
|
EnvironmentName = "ACC",
|
||||||
|
EndpointCatalogPath = Path.Combine(directory, "endpoints.xml"),
|
||||||
|
LogDirectory = Path.Combine(directory, "logs"),
|
||||||
|
ForceEndpointCatalogRefresh = true
|
||||||
|
};
|
||||||
|
var oldTimestamp = DateTime.UtcNow.AddHours(-1);
|
||||||
|
new EndpointCatalogStore(
|
||||||
|
options.EndpointCatalogPath,
|
||||||
|
options.EndpointCatalogMaxBytes,
|
||||||
|
options.EndpointCatalogMaxEntries)
|
||||||
|
.Write(new EndpointCatalog
|
||||||
|
{
|
||||||
|
EnvironmentName = options.EnvironmentName,
|
||||||
|
SynchronizedUtc = oldTimestamp
|
||||||
|
});
|
||||||
|
|
||||||
|
var probeResult = new ProbeResult();
|
||||||
|
probeResult.Platform.ReceiveLocationsDataAvailable = true;
|
||||||
|
probeResult.Platform.SendPortsDataAvailable = true;
|
||||||
|
new EndpointConnectivityProbe(options, new FileLogger(options.LogDirectory, 1, "test"))
|
||||||
|
.Query(probeResult);
|
||||||
|
|
||||||
|
var refreshed = new EndpointCatalogStore(
|
||||||
|
options.EndpointCatalogPath,
|
||||||
|
options.EndpointCatalogMaxBytes,
|
||||||
|
options.EndpointCatalogMaxEntries)
|
||||||
|
.Read(options.EnvironmentName);
|
||||||
|
Assert(probeResult.EndpointConnectivity.RefreshRequired, "forced catalog refresh was not requested");
|
||||||
|
Assert(probeResult.EndpointConnectivity.RefreshSucceeded, "forced catalog refresh failed");
|
||||||
|
Assert(refreshed.SynchronizedUtc > oldTimestamp, "forced catalog refresh did not replace timestamp");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(directory)) Directory.Delete(directory, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnvironmentLabelDoesNotRenameServicesByDefault()
|
private static void EnvironmentLabelDoesNotRenameServicesByDefault()
|
||||||
{
|
{
|
||||||
var options = new MonitoringOptions { EnvironmentName = "ACC" };
|
var options = new MonitoringOptions { EnvironmentName = "ACC" };
|
||||||
@@ -765,7 +817,6 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
return line.Substring(start, end - start);
|
return line.Substring(start, end - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> e694e4c (Guard Checkmk service renames during updates)
|
|
||||||
private static void InstallerUpdatePreservesExistingSettings()
|
private static void InstallerUpdatePreservesExistingSettings()
|
||||||
{
|
{
|
||||||
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.InstallerTests." + Guid.NewGuid().ToString("N"));
|
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.InstallerTests." + Guid.NewGuid().ToString("N"));
|
||||||
@@ -779,6 +830,7 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
source,
|
source,
|
||||||
"<configuration><appSettings>"
|
"<configuration><appSettings>"
|
||||||
+ "<add key=\"EnvironmentName\" value=\"\" />"
|
+ "<add key=\"EnvironmentName\" value=\"\" />"
|
||||||
|
+ "<add key=\"IncludeEnvironmentInServiceName\" value=\"false\" />"
|
||||||
+ "<add key=\"EndpointProbeMaxConcurrency\" value=\"16\" />"
|
+ "<add key=\"EndpointProbeMaxConcurrency\" value=\"16\" />"
|
||||||
+ "<add key=\"EndpointMaxCount\" value=\"100\" />"
|
+ "<add key=\"EndpointMaxCount\" value=\"100\" />"
|
||||||
+ "<add key=\"EndpointCatalogMaxEntries\" value=\"1000\" />"
|
+ "<add key=\"EndpointCatalogMaxEntries\" value=\"1000\" />"
|
||||||
@@ -802,6 +854,7 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
string.Empty);
|
string.Empty);
|
||||||
var merged = File.ReadAllText(staged);
|
var merged = File.ReadAllText(staged);
|
||||||
AssertEqual("ACC", effectiveEnvironment, "preserved installer environment");
|
AssertEqual("ACC", effectiveEnvironment, "preserved installer environment");
|
||||||
|
Assert(merged.Contains("key=\"IncludeEnvironmentInServiceName\" value=\"false\""), "stable service-name default must be added on update");
|
||||||
Assert(merged.Contains("key=\"EndpointProbeMaxConcurrency\" value=\"7\""), "existing operational value must be preserved");
|
Assert(merged.Contains("key=\"EndpointProbeMaxConcurrency\" value=\"7\""), "existing operational value must be preserved");
|
||||||
Assert(merged.Contains("key=\"EndpointMaxCount\" value=\"100\""), "superseded old default must migrate to new bounded default");
|
Assert(merged.Contains("key=\"EndpointMaxCount\" value=\"100\""), "superseded old default must migrate to new bounded default");
|
||||||
Assert(merged.Contains("key=\"EndpointCatalogMaxEntries\" value=\"1000\""), "new catalog entry limit must be added");
|
Assert(merged.Contains("key=\"EndpointCatalogMaxEntries\" value=\"1000\""), "new catalog entry limit must be added");
|
||||||
@@ -821,6 +874,214 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationAcceptsFreshConsistentArtifacts()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
boundary.AddSeconds(5),
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
true,
|
||||||
|
null);
|
||||||
|
Assert(result.IsSuccess, "fresh consistent runtime artifacts must pass: " + result.Error);
|
||||||
|
AssertEqual(9, result.StableServiceCount, "validated stable service count");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationRejectsPreInstallSnapshot()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(-1),
|
||||||
|
boundary.AddSeconds(5),
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
true,
|
||||||
|
null);
|
||||||
|
Assert(!result.IsSuccess, "snapshot from before installation must fail");
|
||||||
|
Assert(result.Error.Contains("nicht aus dem gestarteten Providerlauf"), "old snapshot reason missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationRejectsWrongIdentity()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
boundary.AddSeconds(5),
|
||||||
|
"BEW\\other-account",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
true,
|
||||||
|
null);
|
||||||
|
Assert(!result.IsSuccess, "unexpected provider identity must fail");
|
||||||
|
Assert(result.Error.Contains("unerwarteten Identitaet"), "identity mismatch reason missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationRejectsStaleCatalog()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
boundary.AddSeconds(-1),
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
true,
|
||||||
|
null);
|
||||||
|
Assert(!result.IsSuccess, "catalog from before installation must fail");
|
||||||
|
Assert(result.Error.Contains("nicht aktualisiert"), "old catalog reason missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationRejectsChangedServiceContract()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
lines[0] = lines[0].Replace("BizTalk Platform", "BizTalk Renamed Platform");
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
boundary.AddSeconds(5),
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
true,
|
||||||
|
lines);
|
||||||
|
Assert(!result.IsSuccess, "changed runtime service contract must fail");
|
||||||
|
Assert(result.Error.Contains("Fehlend=[BizTalk Platform]"), "missing stable service reason missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
null,
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
false,
|
||||||
|
null);
|
||||||
|
Assert(result.IsSuccess, "disabled endpoint probe must not require catalog: " + result.Error);
|
||||||
|
Assert(!result.CatalogSynchronizedUtc.HasValue, "disabled endpoint probe must report no catalog timestamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationRejectsUnknownStableService()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
lines[0] = "3" + lines[0].Substring(1);
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
boundary.AddSeconds(5),
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
true,
|
||||||
|
lines);
|
||||||
|
Assert(!result.IsSuccess, "UNKNOWN stable service must fail runtime acceptance");
|
||||||
|
Assert(result.Error.Contains("UNKNOWN=[BizTalk Platform]"), "UNKNOWN stable service reason missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ForceEndpointRefreshRequiresProviderMode()
|
||||||
|
{
|
||||||
|
var options = MonitoringOptions.Load(new[] { "--collect", "--force-endpoint-refresh" });
|
||||||
|
Assert(options.Collect, "forced refresh must retain provider mode");
|
||||||
|
Assert(options.ForceEndpointCatalogRefresh, "forced refresh option missing");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MonitoringOptions.Load(new[] { "--force-endpoint-refresh" });
|
||||||
|
throw new InvalidOperationException("forced refresh without provider mode was accepted");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Assert(ex.Message.Contains("valid only in provider mode"), "forced refresh rejection reason missing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationArgumentsAreParsedStrictly()
|
||||||
|
{
|
||||||
|
var timestamp = DateTime.UtcNow;
|
||||||
|
var identity = "BEW\\t231bizmon";
|
||||||
|
var options = MonitoringOptions.Load(new[]
|
||||||
|
{
|
||||||
|
"--validate-runtime",
|
||||||
|
"--validation-not-before-utc",
|
||||||
|
timestamp.ToString("o"),
|
||||||
|
"--expected-identity-base64",
|
||||||
|
Convert.ToBase64String(Encoding.UTF8.GetBytes(identity))
|
||||||
|
});
|
||||||
|
Assert(options.RuntimeValidation, "runtime validation mode missing");
|
||||||
|
AssertEqual(timestamp.ToString("o"), options.RuntimeValidationNotBeforeUtc.Value.ToString("o"), "runtime validation timestamp");
|
||||||
|
AssertEqual(identity, options.ExpectedRuntimeIdentity, "runtime validation identity");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TaskCompletionRequiresFreshSuccessfulRun()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow;
|
||||||
|
Assert(BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
|
||||||
|
3, 0, boundary.AddSeconds(1), boundary), "fresh successful ready task must pass");
|
||||||
|
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
|
||||||
|
4, 0, boundary.AddSeconds(1), boundary), "running task must not pass");
|
||||||
|
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
|
||||||
|
3, 1, boundary.AddSeconds(1), boundary), "failed task result must not pass");
|
||||||
|
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsSuccessfulCompletion(
|
||||||
|
3, 0, boundary.AddSeconds(-3), boundary), "old successful task must not pass");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RuntimeValidationResult ValidateRuntimeArtifacts(
|
||||||
|
DateTime boundaryUtc,
|
||||||
|
DateTime snapshotUtc,
|
||||||
|
DateTime? catalogUtc,
|
||||||
|
string snapshotIdentity,
|
||||||
|
string expectedIdentity,
|
||||||
|
bool probeEndpoints,
|
||||||
|
string[] snapshotLines)
|
||||||
|
{
|
||||||
|
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.RuntimeValidationTests." + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new MonitoringOptions
|
||||||
|
{
|
||||||
|
EnvironmentName = "ACC",
|
||||||
|
SnapshotPath = Path.Combine(directory, "snapshot.txt"),
|
||||||
|
EndpointCatalogPath = Path.Combine(directory, "endpoints.xml"),
|
||||||
|
ProbeEndpointConnectivity = probeEndpoints
|
||||||
|
};
|
||||||
|
var formatter = new CheckmkLocalFormatter(options);
|
||||||
|
new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes).Write(
|
||||||
|
snapshotLines ?? formatter.FormatSelfTest().ToArray(),
|
||||||
|
snapshotUtc,
|
||||||
|
snapshotIdentity);
|
||||||
|
|
||||||
|
if (catalogUtc.HasValue)
|
||||||
|
{
|
||||||
|
new EndpointCatalogStore(
|
||||||
|
options.EndpointCatalogPath,
|
||||||
|
options.EndpointCatalogMaxBytes,
|
||||||
|
options.EndpointCatalogMaxEntries)
|
||||||
|
.Write(new EndpointCatalog
|
||||||
|
{
|
||||||
|
EnvironmentName = options.EnvironmentName,
|
||||||
|
SynchronizedUtc = catalogUtc.Value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return RuntimeValidator.Validate(
|
||||||
|
options,
|
||||||
|
formatter,
|
||||||
|
boundaryUtc,
|
||||||
|
expectedIdentity,
|
||||||
|
boundaryUtc.AddMinutes(1));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (Directory.Exists(directory)) Directory.Delete(directory, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void SnapshotRejectsTampering()
|
private static void SnapshotRejectsTampering()
|
||||||
{
|
{
|
||||||
WithTemporarySnapshot((path, store) =>
|
WithTemporarySnapshot((path, store) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user