Fix legacy installer updates and diagnostics
This commit is contained in:
@@ -419,6 +419,22 @@ enthalten. Erst dann wird der normale Minutentask registriert und das Backup
|
|||||||
gelöscht. Bei Fehler oder Timeout wird die alte Version wiederhergestellt und
|
gelöscht. Bei Fehler oder Timeout wird die alte Version wiederhergestellt und
|
||||||
ein frischer Lauf des alten Tasks abgewartet.
|
ein frischer Lauf des alten Tasks abgewartet.
|
||||||
|
|
||||||
|
Version 2.2.5 korrigiert die versionsübergreifende Vorprüfung. Die neue
|
||||||
|
Paket-, Staging- und Zielversion muss weiterhin exakt neun gültige
|
||||||
|
Self-Test-Zeilen liefern. Für die installierte Ausgangsversion wird dagegen
|
||||||
|
deren vollständiger, eindeutiger und ausschließlich grüner Servicevertrag
|
||||||
|
akzeptiert, auch wenn er aus einer älteren Version mit acht Services stammt.
|
||||||
|
Rein additive Services blockieren das Update nicht; entfernte Namen und damit
|
||||||
|
auch Renames bleiben vor der Umschaltung bestätigungspflichtig.
|
||||||
|
|
||||||
|
Parallel erzeugt jeder Installations-/Updatelauf ein separates
|
||||||
|
`%ProgramData%\BizTalkCheckmkPulse\logs\setup-*.log`. Protokolliert werden
|
||||||
|
Phase, Setup-/Binary-Version, Pfad und Größe, Self-Test-stdout/-stderr,
|
||||||
|
Exitcodes, Taskstatus, Exception-Kette, letzte Provider-Logzeilen bei einem
|
||||||
|
Abnahmefehler und das Rollback-Ergebnis. Das Collector-Kennwort wird der
|
||||||
|
Logging-Komponente nie übergeben. Ein frisch abgeschlossener Task mit
|
||||||
|
Fehlercode wird sofort erkannt; der Fehler erscheint dezimal und hexadezimal.
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ Der Datenaustausch ist bewusst defensiv:
|
|||||||
`UNKNOWN`-Snapshot und einen ungleich null lautenden Task-Exitcode.
|
`UNKNOWN`-Snapshot und einen ungleich null lautenden Task-Exitcode.
|
||||||
- Provider und Consumer protokollieren in tägliche Dateien; die Aufbewahrung
|
- Provider und Consumer protokollieren in tägliche Dateien; die Aufbewahrung
|
||||||
ist standardmäßig 30 Tage.
|
ist standardmäßig 30 Tage.
|
||||||
|
- Jeder Installations-/Updatelauf schreibt zusätzlich ein eigenes
|
||||||
|
`setup-*.log` mit Phase, Binary-Version, Self-Test-Ausgabe,
|
||||||
|
Task-Scheduler-Status, Exception-Kette und Rollback-Ergebnis. Kennwörter
|
||||||
|
werden nicht an das Logging übergeben.
|
||||||
- Snapshotgröße, WMI-/SQL-Timeouts, Log-Retention und Stale-Grenze sind
|
- Snapshotgröße, WMI-/SQL-Timeouts, Log-Retention und Stale-Grenze sind
|
||||||
begrenzt und konfigurierbar.
|
begrenzt und konfigurierbar.
|
||||||
|
|
||||||
@@ -263,19 +267,23 @@ als `UNKNOWN`.
|
|||||||
Ein gMSA kann über die Checkbox ohne Kennworteingabe installiert werden; die
|
Ein gMSA kann über die Checkbox ohne Kennworteingabe installiert werden; die
|
||||||
produktive Standardbeschreibung geht vom normalen Servicekonto aus.
|
produktive Standardbeschreibung geht vom normalen Servicekonto aus.
|
||||||
|
|
||||||
Der Installer:
|
Der Installer (ab Version 2.2.5):
|
||||||
|
|
||||||
1. prüft Paket und neun Self-Test-Zeilen, bevor der bestehende Task angehalten
|
1. prüft Paket und Staging zwingend auf neun Self-Test-Zeilen, bevor der
|
||||||
wird,
|
bestehende Task angehalten wird. Der Servicevertrag der installierten
|
||||||
|
Legacy-Version darf weniger gültige Zeilen enthalten,
|
||||||
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. vergleicht installierten und neuen Checkmk-Servicevertrag; ein
|
3. vergleicht installierten und neuen Checkmk-Servicevertrag. Rein additive
|
||||||
unbestätigter Rename stoppt vor jeder Umschaltung,
|
Services werden zugelassen und als Discovery-Hinweis gemeldet; entfernte
|
||||||
|
oder umbenannte Services stoppen ohne ausdrückliche Bestätigung vor jeder
|
||||||
|
Umschaltung,
|
||||||
4. schaltet die Programmdateien per Verzeichniswechsel um,
|
4. schaltet die Programmdateien per Verzeichniswechsel um,
|
||||||
5. startet einen triggerlosen Provider-Abnahmelauf unter dem echten
|
5. startet einen triggerlosen Provider-Abnahmelauf unter dem echten
|
||||||
Collector-Konto und erzwingt einen vollständigen Endpoint-Katalogabgleich,
|
Collector-Konto und erzwingt einen vollständigen Endpoint-Katalogabgleich,
|
||||||
6. wartet höchstens vier Minuten auf `LastTaskResult=0` und validiert danach
|
6. wartet höchstens vier Minuten auf `LastTaskResult=0`, bricht bei einem
|
||||||
|
bereits abgeschlossenen Fehlerlauf sofort mit Dezimal-/Hexcode ab und validiert danach
|
||||||
mit der Produkt-EXE einen erst nach Installationsbeginn erzeugten Snapshot,
|
mit der Produkt-EXE einen erst nach Installationsbeginn erzeugten Snapshot,
|
||||||
die Collector-Identität, alle neun eindeutigen Services ohne `UNKNOWN`
|
die Collector-Identität, alle neun eindeutigen Services ohne `UNKNOWN`
|
||||||
sowie den frischen, maschinen- und umgebungsgebundenen Endpoint-Katalog,
|
sowie den frischen, maschinen- und umgebungsgebundenen Endpoint-Katalog,
|
||||||
@@ -283,7 +291,8 @@ Der Installer:
|
|||||||
werden vorherige Version, Wrapper und Task samt frischem Altsnapshot
|
werden vorherige Version, Wrapper und Task samt frischem Altsnapshot
|
||||||
wiederhergestellt,
|
wiederhergestellt,
|
||||||
8. behält `%ProgramData%\BizTalkCheckmkPulse` mit Endpoint-Katalog, Snapshot
|
8. behält `%ProgramData%\BizTalkCheckmkPulse` mit Endpoint-Katalog, Snapshot
|
||||||
und Logs bei,
|
und Logs bei. Das laufbezogene Setup-Log enthält auch die vollständige
|
||||||
|
Self-Test-Ausgabe sowie bei Abnahmefehlern die letzten Provider-Logzeilen,
|
||||||
9. installiert EXE und Config nach `%ProgramFiles%\BizTalkCheckmkPulse`, setzt
|
9. installiert EXE und Config nach `%ProgramFiles%\BizTalkCheckmkPulse`, setzt
|
||||||
explizite ACLs für Administratoren, Provider und `LocalSystem` und
|
explizite ACLs für Administratoren, Provider und `LocalSystem` und
|
||||||
installiert nur den kleinen `.cmd`-Consumer unter
|
installiert nur den kleinen `.cmd`-Consumer unter
|
||||||
@@ -306,8 +315,14 @@ Provider-Log:
|
|||||||
```cmd
|
```cmd
|
||||||
dir /o-d "%ProgramData%\BizTalkCheckmkPulse\logs"
|
dir /o-d "%ProgramData%\BizTalkCheckmkPulse\logs"
|
||||||
type "%ProgramData%\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log"
|
type "%ProgramData%\BizTalkCheckmkPulse\logs\biztalk-checkmk-pulse-*.log"
|
||||||
|
type "%ProgramData%\BizTalkCheckmkPulse\logs\setup-*.log"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Die Analyse des in Version 2.2.4 beobachteten Abbruchs mit
|
||||||
|
`Exitcode=0, Zeilen=8` ist in
|
||||||
|
[docs/Installer-Self-Test-Analyse-2026-08-11.md](docs/Installer-Self-Test-Analyse-2026-08-11.md)
|
||||||
|
dokumentiert.
|
||||||
|
|
||||||
Snapshot und Consumer:
|
Snapshot und Consumer:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
|
|||||||
@@ -28,14 +28,23 @@ nach dem nächsten Agentenlauf das 2.2.1-Fehlerbild `Item not found in
|
|||||||
monitoring data`. Nur bei bewusst auf `true` gesetztem Opt-in ist anschließend
|
monitoring data`. Nur bei bewusst auf `true` gesetztem Opt-in ist anschließend
|
||||||
eine Checkmk Service Discovery erforderlich.
|
eine Checkmk Service Discovery erforderlich.
|
||||||
|
|
||||||
Ab Version 2.2.3 vergleicht der Installer bei jedem Update die exakten
|
Ab Version 2.2.5 vergleicht der Installer bei jedem Update die exakten
|
||||||
Servicenamen der installierten und der vorbereiteten Version. Neue, entfernte
|
Servicenamen der installierten und der vorbereiteten Version. Rein neue
|
||||||
oder umbenannte Services führen **vor dem Stoppen des Tasks** zu einem
|
Services sind abwärtskompatibel, blockieren das Update nicht und werden als
|
||||||
Sicherheitsstopp. Die Installation darf nur über die Checkbox
|
Hinweis für die anschließende Service Discovery angezeigt. Entfernte oder
|
||||||
|
umbenannte Services führen **vor dem Stoppen des Tasks** zu einem
|
||||||
|
Sicherheitsstopp. Die Installation darf dann nur über die Checkbox
|
||||||
**Service-Rename ist beabsichtigt; Checkmk Service Discovery ist eingeplant**
|
**Service-Rename ist beabsichtigt; Checkmk Service Discovery ist eingeplant**
|
||||||
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.5 akzeptiert außerdem den gültigen Self-Test einer installierten
|
||||||
|
Legacy-Version mit weniger Services. Paket, Staging und aktivierte Zielversion
|
||||||
|
müssen weiterhin exakt neun Services liefern. Damit kann insbesondere eine
|
||||||
|
Version mit acht Services auf die Version mit dem zusätzlichen Service
|
||||||
|
`BizTalk Endpoint Reachability` aktualisiert werden. Der frühere Abbruch
|
||||||
|
`Exitcode=0, Zeilen=8` betraf die Altversion und war kein Fehler im neuen Paket.
|
||||||
|
|
||||||
Version 2.2.4 nimmt die umgeschaltete Installation zusätzlich vollständig ab:
|
Version 2.2.4 nimmt die umgeschaltete Installation zusätzlich vollständig ab:
|
||||||
|
|
||||||
- einmaliger, triggerloser Providerlauf unter dem angegebenen Collector-Konto,
|
- einmaliger, triggerloser Providerlauf unter dem angegebenen Collector-Konto,
|
||||||
@@ -55,6 +64,16 @@ Programmversion samt Task wiederhergestellt und ein frischer Lauf der alten
|
|||||||
Version abgewartet. Fehler in Paket-, Config- oder Rename-Vorprüfung treten vor
|
Version abgewartet. Fehler in Paket-, Config- oder Rename-Vorprüfung treten vor
|
||||||
jeder Taskänderung auf und lassen die laufende Installation unangetastet.
|
jeder Taskänderung auf und lassen die laufende Installation unangetastet.
|
||||||
|
|
||||||
|
Jeder Installations-/Updatelauf schreibt ein separates Diagnoselog nach
|
||||||
|
`%ProgramData%\BizTalkCheckmkPulse\logs\setup-*.log`. Es enthält die aktuelle
|
||||||
|
Phase, Pfad und Version jeder geprüften EXE, Exitcode, vollständiges stdout und
|
||||||
|
stderr des Self-Tests, Taskstatus samt Dezimal-/Hexcode, Exception-Kette und
|
||||||
|
Rollback-Ergebnis. Bei einem Runtime-Abnahmefehler werden zusätzlich die
|
||||||
|
letzten Provider-Logzeilen übernommen. Das Collector-Kennwort wird weder an
|
||||||
|
das Logging übergeben noch protokolliert. Ein abgeschlossener Providerlauf mit
|
||||||
|
Fehlercode beendet die Abnahme sofort; nur ein noch laufender Task wird bis zur
|
||||||
|
Timeoutgrenze abgewartet.
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ Endpoint-/Namensfix bewusst nicht grüngefärbt.
|
|||||||
|
|
||||||
## ACC-Abnahme nach Installation
|
## ACC-Abnahme nach Installation
|
||||||
|
|
||||||
1. Setup 2.2.4 als Update ausführen; Umgebung `ACC` kann bestehen bleiben.
|
1. Setup 2.2.5 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.
@@ -0,0 +1,68 @@
|
|||||||
|
# Analyse: Update-Abbruch mit `Exitcode=0, Zeilen=8`
|
||||||
|
|
||||||
|
## Beobachtung
|
||||||
|
|
||||||
|
Der Installer meldete nach erfolgreicher Paketprüfung, Anmeldung und
|
||||||
|
Staging-Validierung:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Installation/Update fehlgeschlagen. Rollback erfolgreich.
|
||||||
|
Ursache: Self-Test fehlgeschlagen. Exitcode=0, Zeilen=8.
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Reihenfolge im Statusfenster zeigt, dass das neue Paket und das Staging
|
||||||
|
bereits erfolgreich neun Services geliefert hatten. Der danach ausgeführte
|
||||||
|
Self-Test gehörte zur noch installierten Ausgangsversion.
|
||||||
|
|
||||||
|
## Ursache
|
||||||
|
|
||||||
|
Version 2.2.4 verwendete dieselbe starre Erwartung von exakt neun Zeilen für
|
||||||
|
beide Seiten des Updatevergleichs. Eine ältere, intakte Installation vor
|
||||||
|
Einführung von `BizTalk Endpoint Reachability` liefert jedoch acht gültige
|
||||||
|
Checkmk-Zeilen und Exitcode 0. Der Updater deutete diese erwartete
|
||||||
|
Versionsdifferenz fälschlich als defekten Self-Test. Der Abbruch erfolgte vor
|
||||||
|
dem Stoppen des Tasks und vor dem Austausch von Dateien. Die Meldung
|
||||||
|
`Rollback erfolgreich` war deshalb technisch irreführend; es war keine
|
||||||
|
Systemänderung zurückzusetzen.
|
||||||
|
|
||||||
|
## Korrektur in Version 2.2.5
|
||||||
|
|
||||||
|
- Paket, Staging und aktivierte Zielversion müssen weiterhin exakt neun
|
||||||
|
eindeutige `OK`-Zeilen liefern.
|
||||||
|
- Die installierte Ausgangsversion darf eine andere positive Anzahl
|
||||||
|
eindeutiger `OK`-Zeilen liefern. Damit wird ihr tatsächlicher
|
||||||
|
versionsspezifischer Servicevertrag gelesen.
|
||||||
|
- Rein neu hinzukommende Services sind updatekompatibel. Sie werden als
|
||||||
|
Discovery-Hinweis gemeldet, blockieren aber die Umschaltung nicht.
|
||||||
|
- Entfernte Namen – und damit auch Renames – bleiben ohne aktivierte
|
||||||
|
Discovery-Bestätigung ein Sicherheitsstopp.
|
||||||
|
- Tritt ein Fehler vor der ersten Systemänderung auf, lautet der Status jetzt
|
||||||
|
`kein Rollback erforderlich`.
|
||||||
|
|
||||||
|
## Erweiterte Diagnose
|
||||||
|
|
||||||
|
Jeder Lauf erzeugt
|
||||||
|
`%ProgramData%\BizTalkCheckmkPulse\logs\setup-<UTC>-<PID>.log`. Das Log enthält:
|
||||||
|
|
||||||
|
- aktuelle Installationsphase und Setup-Version,
|
||||||
|
- Pfad, Dateiversion und Größe jeder geprüften EXE,
|
||||||
|
- erwartete und tatsächliche Zeilenzahl, Exitcode sowie vollständiges stdout
|
||||||
|
und stderr jedes Self-Tests,
|
||||||
|
- Task-Scheduler-State und `LastTaskResult` dezimal und hexadezimal,
|
||||||
|
- vollständige Exception-Kette mit HRESULT und Stacktrace,
|
||||||
|
- bei Runtime-Abnahmefehlern bis zu 40 letzte Provider-Logzeilen,
|
||||||
|
- Ergebnis jedes Rollback-Schritts.
|
||||||
|
|
||||||
|
Das Kennwort wird ausschließlich an `LogonUser` und die
|
||||||
|
Task-Scheduler-COM-Schnittstelle übergeben. Die Logging-Komponente erhält es
|
||||||
|
nicht.
|
||||||
|
|
||||||
|
## Regressionstests
|
||||||
|
|
||||||
|
Automatisiert geprüft werden insbesondere:
|
||||||
|
|
||||||
|
- acht gültige Zeilen einer Legacy-Ausgangsversion werden akzeptiert,
|
||||||
|
- acht Zeilen eines neuen Stagings werden weiterhin abgewiesen,
|
||||||
|
- eine reine Service-Erweiterung wird ohne Rename-Bestätigung akzeptiert,
|
||||||
|
- ein frisch beendeter Task mit Fehlercode wird sofort erkannt und der Code
|
||||||
|
enthält zusätzlich die hexadezimale Darstellung.
|
||||||
+9
-1
@@ -53,7 +53,7 @@ Verbindlicher Agent-Dump:
|
|||||||
|
|
||||||
## Service Discovery
|
## Service Discovery
|
||||||
|
|
||||||
Das Setup 2.2.4 schließt erst erfolgreich ab, nachdem der erste Providerlauf
|
Das Setup ab 2.2.4 schließt erst erfolgreich ab, nachdem der erste Providerlauf
|
||||||
unter dem echten Collector-Konto einen frischen Snapshot und – bei aktivierter
|
unter dem echten Collector-Konto einen frischen Snapshot und – bei aktivierter
|
||||||
Endpoint-Prüfung – einen frisch synchronisierten Katalog erzeugt hat. Die neun
|
Endpoint-Prüfung – einen frisch synchronisierten Katalog erzeugt hat. Die neun
|
||||||
stabilen Services müssen vollständig und ohne `UNKNOWN` vorliegen. Diese lokale
|
stabilen Services müssen vollständig und ohne `UNKNOWN` vorliegen. Diese lokale
|
||||||
@@ -64,6 +64,14 @@ Servicenamen aber standardmäßig nicht. Nur das bewusste Opt-in
|
|||||||
`IncludeEnvironmentInServiceName=true` erzeugt umgebungsabhängige Namen und
|
`IncludeEnvironmentInServiceName=true` erzeugt umgebungsabhängige Namen und
|
||||||
verlangt den anschließenden Discovery-Abgleich.
|
verlangt den anschließenden Discovery-Abgleich.
|
||||||
|
|
||||||
|
Ab Setup 2.2.5 darf der gültige Servicevertrag der installierten
|
||||||
|
Ausgangsversion weniger als neun Services enthalten. So ist das Update einer
|
||||||
|
Legacy-Version mit acht Services möglich; nur Paket, Staging und Zielversion
|
||||||
|
müssen exakt neun Services liefern. Additive Services werden gemeldet, aber
|
||||||
|
nicht blockiert. Entfernte oder umbenannte Services erfordern weiterhin die
|
||||||
|
bewusste Bestätigung im Setup. Laufbezogene Details stehen in
|
||||||
|
`%ProgramData%\BizTalkCheckmkPulse\logs\setup-*.log`.
|
||||||
|
|
||||||
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.4 | Stand 10.08.2026 | Seite "), size=7.5, color=MUTED)
|
format_run(p.add_run("Version 2.2.5 | Stand 11.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.4"
|
core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.5"
|
||||||
|
|
||||||
# Cover
|
# Cover
|
||||||
banner = document.add_table(rows=1, cols=1)
|
banner = document.add_table(rows=1, cols=1)
|
||||||
@@ -377,9 +377,9 @@ def build_document():
|
|||||||
document,
|
document,
|
||||||
("Dokumentmerkmal", "Angabe"),
|
("Dokumentmerkmal", "Angabe"),
|
||||||
(
|
(
|
||||||
("Lösungsstand", "BizTalk Checkmk Pulse 2.2.4"),
|
("Lösungsstand", "BizTalk Checkmk Pulse 2.2.5"),
|
||||||
("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", "11. August 2026"),
|
||||||
("Dokumentzweck", "Architektur- und Leistungsüberblick für technische Stakeholder"),
|
("Dokumentzweck", "Architektur- und Leistungsüberblick für technische Stakeholder"),
|
||||||
),
|
),
|
||||||
(4.2, 13.0),
|
(4.2, 13.0),
|
||||||
@@ -543,7 +543,8 @@ def build_document():
|
|||||||
add_bullet(document, "Getrennte Tageslogs für Provider und Consumer; Standardaufbewahrung 30 Tage.")
|
add_bullet(document, "Getrennte Tageslogs für Provider und Consumer; Standardaufbewahrung 30 Tage.")
|
||||||
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 validiert neue Versionen auf exakt neun Services, akzeptiert vollständige Legacy-Verträge mit weniger Services und stoppt entfernte oder umbenannte Servicenamen vor jeder Umschaltung.")
|
||||||
|
add_bullet(document, "Ein laufbezogenes Setup-Log protokolliert Phase, Binary-Version, Self-Test-Ausgabe, Taskcodes, Exception-Kette und Rollback ohne Kennwort.")
|
||||||
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_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)
|
||||||
@@ -573,7 +574,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.4 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.5 sowie Checkmk User Guide „Local checks“, abgerufen am 10. August 2026.", {"size": 7.8, "color": MUTED}),),
|
||||||
before=8,
|
before=8,
|
||||||
after=0,
|
after=0,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<Compile Include="InstallerEngine.cs" />
|
<Compile Include="InstallerEngine.cs" />
|
||||||
<Compile Include="MainForm.cs" />
|
<Compile Include="MainForm.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="SetupOperationLog.cs" />
|
||||||
<Compile Include="TaskSchedulerService.cs" />
|
<Compile Include="TaskSchedulerService.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
@@ -37,26 +38,32 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
bool allowServiceNameChange,
|
bool allowServiceNameChange,
|
||||||
Action<string> report)
|
Action<string> report)
|
||||||
{
|
{
|
||||||
Validate(account, password, isGmsa, environmentName);
|
var operationStartedUtc = DateTime.UtcNow;
|
||||||
report = report ?? delegate { };
|
var uiReport = report ?? delegate { };
|
||||||
var accountSid = (SecurityIdentifier)new NTAccount(account).Translate(typeof(SecurityIdentifier));
|
var setupLog = SetupOperationLog.Create(runtimeDirectory, "install-or-update");
|
||||||
report("Konto aufgeloest: " + account + " (" + accountSid.Value + ")");
|
report = delegate(string message)
|
||||||
|
{
|
||||||
|
setupLog.Write("INFO", message);
|
||||||
|
uiReport(message);
|
||||||
|
};
|
||||||
|
if (setupLog.FilePath.Length > 0)
|
||||||
|
report("Diagnoselog: " + setupLog.FilePath);
|
||||||
|
else
|
||||||
|
uiReport("WARNUNG: Diagnoselog konnte nicht angelegt werden: " + setupLog.CreationError);
|
||||||
|
|
||||||
|
var phase = "Initialisierung";
|
||||||
var sourceApplication = Path.Combine(packageDirectory, "application");
|
var sourceApplication = Path.Combine(packageDirectory, "application");
|
||||||
var sourceExe = Path.Combine(sourceApplication, "BizTalkCheckmkPulse.exe");
|
var sourceExe = Path.Combine(sourceApplication, "BizTalkCheckmkPulse.exe");
|
||||||
var sourceConfig = sourceExe + ".config";
|
var sourceConfig = sourceExe + ".config";
|
||||||
var sourceWrapper = Path.Combine(packageDirectory, "biztalk_checkmk_pulse.cmd");
|
var sourceWrapper = Path.Combine(packageDirectory, "biztalk_checkmk_pulse.cmd");
|
||||||
RequireFile(sourceExe);
|
|
||||||
RequireFile(sourceConfig);
|
|
||||||
RequireFile(sourceWrapper);
|
|
||||||
var targetExe = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe");
|
var targetExe = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe");
|
||||||
var targetConfig = targetExe + ".config";
|
var targetConfig = targetExe + ".config";
|
||||||
var stagingDirectory = installDirectory + ".staging." + Guid.NewGuid().ToString("N");
|
var stagingDirectory = installDirectory + ".staging." + Guid.NewGuid().ToString("N");
|
||||||
var backupDirectory = installDirectory + ".backup." + Guid.NewGuid().ToString("N");
|
var backupDirectory = installDirectory + ".backup." + Guid.NewGuid().ToString("N");
|
||||||
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;
|
byte[] previousWrapper = null;
|
||||||
var hadExistingInstallation = Directory.Exists(installDirectory);
|
var hadExistingInstallation = false;
|
||||||
var hadExistingExecutable = File.Exists(targetExe);
|
var hadExistingExecutable = false;
|
||||||
var mutationStarted = false;
|
var mutationStarted = false;
|
||||||
var backupCreated = false;
|
var backupCreated = false;
|
||||||
var filesActivated = false;
|
var filesActivated = false;
|
||||||
@@ -64,14 +71,29 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RunSelfTest(sourceExe);
|
phase = "Eingaben und Paket pruefen";
|
||||||
|
report("Phase: " + phase + ". Paket=" + packageDirectory + ".");
|
||||||
|
Validate(account, password, isGmsa, environmentName);
|
||||||
|
var accountSid = (SecurityIdentifier)new NTAccount(account).Translate(typeof(SecurityIdentifier));
|
||||||
|
report("Konto aufgeloest: " + account + " (" + accountSid.Value + ")");
|
||||||
|
RequireFile(sourceExe);
|
||||||
|
RequireFile(sourceConfig);
|
||||||
|
RequireFile(sourceWrapper);
|
||||||
|
previousWrapper = File.Exists(targetWrapper) ? File.ReadAllBytes(targetWrapper) : null;
|
||||||
|
hadExistingInstallation = Directory.Exists(installDirectory);
|
||||||
|
hadExistingExecutable = File.Exists(targetExe);
|
||||||
|
|
||||||
|
RunSelfTest(sourceExe, 9, "Paket", setupLog);
|
||||||
report("Paket-Vorpruefung erfolgreich: neun Checkmk-Services.");
|
report("Paket-Vorpruefung erfolgreich: neun Checkmk-Services.");
|
||||||
if (!isGmsa)
|
if (!isGmsa)
|
||||||
{
|
{
|
||||||
|
phase = "Collector-Anmeldung pruefen";
|
||||||
ValidateBatchLogon(account, password);
|
ValidateBatchLogon(account, password);
|
||||||
report("Collector-Anmeldung und 'Log on as a batch job' vor dem Update bestaetigt.");
|
report("Collector-Anmeldung und 'Log on as a batch job' vor dem Update bestaetigt.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
phase = "Update-Staging vorbereiten";
|
||||||
|
report("Phase: " + phase + ".");
|
||||||
Directory.CreateDirectory(stagingDirectory);
|
Directory.CreateDirectory(stagingDirectory);
|
||||||
var stagedExe = Path.Combine(stagingDirectory, "BizTalkCheckmkPulse.exe");
|
var stagedExe = Path.Combine(stagingDirectory, "BizTalkCheckmkPulse.exe");
|
||||||
var stagedConfig = stagedExe + ".config";
|
var stagedConfig = stagedExe + ".config";
|
||||||
@@ -81,38 +103,45 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
stagedConfig,
|
stagedConfig,
|
||||||
File.Exists(targetConfig) ? targetConfig : null,
|
File.Exists(targetConfig) ? targetConfig : null,
|
||||||
environmentName);
|
environmentName);
|
||||||
var stagedServiceNames = RunSelfTest(stagedExe);
|
var stagedServiceNames = RunSelfTest(stagedExe, 9, "Staging", setupLog);
|
||||||
report("Update-Staging validiert. Umgebung=" + (effectiveEnvironment.Length == 0 ? "(keine)" : effectiveEnvironment) + ".");
|
report("Update-Staging validiert. Umgebung=" + (effectiveEnvironment.Length == 0 ? "(keine)" : effectiveEnvironment) + ".");
|
||||||
|
|
||||||
if (File.Exists(targetExe))
|
if (File.Exists(targetExe))
|
||||||
{
|
{
|
||||||
var installedServiceNames = RunSelfTest(targetExe);
|
phase = "Installierten Servicevertrag vergleichen";
|
||||||
|
report("Phase: " + phase + ". Legacy-Versionen mit weniger stabilen Services werden als Updatequelle akzeptiert.");
|
||||||
|
var installedServiceNames = RunSelfTest(targetExe, null, "Installierte Version", setupLog);
|
||||||
var serviceNameChange = EnsureServiceNameCompatibility(
|
var serviceNameChange = EnsureServiceNameCompatibility(
|
||||||
installedServiceNames,
|
installedServiceNames,
|
||||||
stagedServiceNames,
|
stagedServiceNames,
|
||||||
allowServiceNameChange);
|
allowServiceNameChange);
|
||||||
report(serviceNameChange.Length == 0
|
report(serviceNameChange.Length == 0
|
||||||
? "Checkmk-Servicevertrag unveraendert: keine Service Discovery erforderlich."
|
? "Checkmk-Servicevertrag unveraendert: keine Service Discovery erforderlich."
|
||||||
: "Checkmk-Service-Rename ausdruecklich bestaetigt; Service Discovery erforderlich. " + serviceNameChange);
|
: "Checkmk-Servicevertrag geaendert; Service Discovery erforderlich. " + serviceNameChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
|
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
|
||||||
|
phase = "Vorhandenen Scheduled Task anhalten";
|
||||||
|
report("Phase: " + phase + ".");
|
||||||
mutationStarted = true;
|
mutationStarted = true;
|
||||||
scheduler.DeleteIfExists(TaskName);
|
scheduler.DeleteIfExists(TaskName);
|
||||||
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
|
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
|
||||||
|
|
||||||
if (hadExistingInstallation)
|
if (hadExistingInstallation)
|
||||||
{
|
{
|
||||||
|
phase = "Vorhandene Programmversion sichern";
|
||||||
Directory.Move(installDirectory, backupDirectory);
|
Directory.Move(installDirectory, backupDirectory);
|
||||||
backupCreated = true;
|
backupCreated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
phase = "Neue Programmversion aktivieren";
|
||||||
Directory.Move(stagingDirectory, installDirectory);
|
Directory.Move(stagingDirectory, installDirectory);
|
||||||
filesActivated = true;
|
filesActivated = true;
|
||||||
report(hadExistingInstallation
|
report(hadExistingInstallation
|
||||||
? "Programmdateien atomar auf die neue Version umgestellt."
|
? "Programmdateien atomar auf die neue Version umgestellt."
|
||||||
: "Programmdateien installiert: " + installDirectory);
|
: "Programmdateien installiert: " + installDirectory);
|
||||||
|
|
||||||
|
phase = "Runtime-Verzeichnisse und ACLs einrichten";
|
||||||
var dataDirectory = Path.Combine(runtimeDirectory, "data");
|
var dataDirectory = Path.Combine(runtimeDirectory, "data");
|
||||||
var logDirectory = Path.Combine(runtimeDirectory, "logs");
|
var logDirectory = Path.Combine(runtimeDirectory, "logs");
|
||||||
Directory.CreateDirectory(runtimeDirectory);
|
Directory.CreateDirectory(runtimeDirectory);
|
||||||
@@ -125,13 +154,16 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
ApplyDirectoryAcl(logDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.Modify);
|
ApplyDirectoryAcl(logDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.Modify);
|
||||||
report("Least-Privilege-Verzeichnisrechte gesetzt; vorhandene Runtime-Daten bleiben erhalten.");
|
report("Least-Privilege-Verzeichnisrechte gesetzt; vorhandene Runtime-Daten bleiben erhalten.");
|
||||||
|
|
||||||
|
phase = "Checkmk Local Check installieren";
|
||||||
Directory.CreateDirectory(checkmkLocalDirectory);
|
Directory.CreateDirectory(checkmkLocalDirectory);
|
||||||
File.Copy(sourceWrapper, targetWrapper, true);
|
File.Copy(sourceWrapper, targetWrapper, true);
|
||||||
report("Checkmk Local Check installiert: " + checkmkLocalDirectory);
|
report("Checkmk Local Check installiert: " + checkmkLocalDirectory);
|
||||||
|
|
||||||
RunSelfTest(targetExe);
|
phase = "Installierte Programmversion pruefen";
|
||||||
|
RunSelfTest(targetExe, 9, "Aktivierte Version", setupLog);
|
||||||
report("Installierter Self-Test erfolgreich: neun Checkmk-Services.");
|
report("Installierter Self-Test erfolgreich: neun Checkmk-Services.");
|
||||||
|
|
||||||
|
phase = "Provider-Abnahmelauf registrieren und starten";
|
||||||
var runtimeValidationStartedUtc = DateTime.UtcNow;
|
var runtimeValidationStartedUtc = DateTime.UtcNow;
|
||||||
scheduler.RegisterValidationAndStart(
|
scheduler.RegisterValidationAndStart(
|
||||||
TaskName,
|
TaskName,
|
||||||
@@ -142,6 +174,7 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
isGmsa);
|
isGmsa);
|
||||||
report("Einmaliger Provider-Abnahmelauf mit erzwungenem Endpoint-Katalogabgleich gestartet.");
|
report("Einmaliger Provider-Abnahmelauf mit erzwungenem Endpoint-Katalogabgleich gestartet.");
|
||||||
|
|
||||||
|
phase = "Provider-Abnahmelauf abwarten";
|
||||||
var completedRun = scheduler.WaitForSuccessfulRun(
|
var completedRun = scheduler.WaitForSuccessfulRun(
|
||||||
TaskName,
|
TaskName,
|
||||||
runtimeValidationStartedUtc,
|
runtimeValidationStartedUtc,
|
||||||
@@ -149,12 +182,14 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
report);
|
report);
|
||||||
report("Provider-Abnahmelauf erfolgreich: LastTaskResult=" + completedRun.ExitCode + ".");
|
report("Provider-Abnahmelauf erfolgreich: LastTaskResult=" + completedRun.ExitCode + ".");
|
||||||
|
|
||||||
|
phase = "Runtime-Artefakte validieren";
|
||||||
var runtimeValidation = RunRuntimeValidation(
|
var runtimeValidation = RunRuntimeValidation(
|
||||||
targetExe,
|
targetExe,
|
||||||
runtimeValidationStartedUtc,
|
runtimeValidationStartedUtc,
|
||||||
account);
|
account);
|
||||||
report("Post-Install-Runtime-Abnahme erfolgreich: " + runtimeValidation);
|
report("Post-Install-Runtime-Abnahme erfolgreich: " + runtimeValidation);
|
||||||
|
|
||||||
|
phase = "Minuetlichen Scheduled Task registrieren";
|
||||||
scheduler.RegisterRecurring(
|
scheduler.RegisterRecurring(
|
||||||
TaskName,
|
TaskName,
|
||||||
targetExe,
|
targetExe,
|
||||||
@@ -165,19 +200,28 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
report("Validierter minuetlicher Scheduled Task registriert: " + TaskName);
|
report("Validierter minuetlicher Scheduled Task registriert: " + TaskName);
|
||||||
|
|
||||||
TryDeleteDirectory(backupDirectory, report);
|
TryDeleteDirectory(backupDirectory, report);
|
||||||
|
report("Installation/Update erfolgreich abgeschlossen.");
|
||||||
}
|
}
|
||||||
catch (Exception installException)
|
catch (Exception installException)
|
||||||
{
|
{
|
||||||
|
setupLog.WriteException(phase, installException);
|
||||||
|
if (phase.StartsWith("Provider-", StringComparison.Ordinal)
|
||||||
|
|| phase.StartsWith("Runtime-", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
WriteRecentProviderLog(setupLog, runtimeDirectory, operationStartedUtc);
|
||||||
|
}
|
||||||
var rollbackFailures = new List<string>();
|
var rollbackFailures = new List<string>();
|
||||||
if (mutationStarted)
|
if (mutationStarted)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
scheduler.DeleteIfExists(TaskName);
|
scheduler.DeleteIfExists(TaskName);
|
||||||
|
report("Rollback: Scheduled Task gestoppt und entfernt.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
rollbackFailures.Add("Task stoppen: " + ex.Message);
|
rollbackFailures.Add("Task stoppen: " + ex.Message);
|
||||||
|
setupLog.WriteException("Rollback: Task stoppen", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +236,7 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
rollbackFailures.Add("Programmdateien wiederherstellen: " + ex.Message);
|
rollbackFailures.Add("Programmdateien wiederherstellen: " + ex.Message);
|
||||||
|
setupLog.WriteException("Rollback: Programmdateien wiederherstellen", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,10 +245,12 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
RestoreWrapper(targetWrapper, previousWrapper);
|
RestoreWrapper(targetWrapper, previousWrapper);
|
||||||
|
report("Rollback: Checkmk-Wrapper auf den vorherigen Stand zurueckgesetzt.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
|
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
|
||||||
|
setupLog.WriteException("Rollback: Checkmk-Wrapper wiederherstellen", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,19 +276,28 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
rollbackFailures.Add("Scheduled Task wiederherstellen: " + ex.Message);
|
rollbackFailures.Add("Scheduled Task wiederherstellen: " + ex.Message);
|
||||||
|
setupLog.WriteException("Rollback: Scheduled Task wiederherstellen", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rollback = rollbackFailures.Count == 0
|
var rollback = !mutationStarted
|
||||||
|
? "Keine Systemaenderung vorgenommen; kein Rollback erforderlich."
|
||||||
|
: rollbackFailures.Count == 0
|
||||||
? "Rollback erfolgreich."
|
? "Rollback erfolgreich."
|
||||||
: "Rollback unvollstaendig: " + string.Join(" | ", rollbackFailures);
|
: "Rollback unvollstaendig: " + string.Join(" | ", rollbackFailures);
|
||||||
|
setupLog.Write("ERROR", rollback);
|
||||||
|
var logReference = setupLog.FilePath.Length == 0
|
||||||
|
? "Diagnoselog nicht verfuegbar (" + setupLog.CreationError + ")."
|
||||||
|
: "Diagnoselog: " + setupLog.FilePath;
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"Installation/Update fehlgeschlagen. " + rollback + " Ursache: " + installException.Message,
|
"Installation/Update fehlgeschlagen in Phase '" + phase + "'. "
|
||||||
|
+ rollback + " Ursache: " + installException.Message + " " + logReference,
|
||||||
installException);
|
installException);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
TryDeleteDirectory(stagingDirectory, null);
|
TryDeleteDirectory(stagingDirectory, null);
|
||||||
|
setupLog.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,8 +508,20 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
new DirectoryInfo(path).SetAccessControl(security);
|
new DirectoryInfo(path).SetAccessControl(security);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<string> RunSelfTest(string executable)
|
private static IReadOnlyList<string> RunSelfTest(
|
||||||
|
string executable,
|
||||||
|
int? expectedServiceCount,
|
||||||
|
string label,
|
||||||
|
SetupOperationLog setupLog)
|
||||||
{
|
{
|
||||||
|
var version = FileVersionInfo.GetVersionInfo(executable).FileVersion ?? "(unbekannt)";
|
||||||
|
setupLog.Write(
|
||||||
|
"INFO",
|
||||||
|
"Self-Test startet. label=" + label
|
||||||
|
+ ", path=" + executable
|
||||||
|
+ ", file_version=" + version
|
||||||
|
+ ", size_bytes=" + new FileInfo(executable).Length
|
||||||
|
+ ", expected_lines=" + (expectedServiceCount.HasValue ? expectedServiceCount.Value.ToString(CultureInfo.InvariantCulture) : "legacy-compatible") + ".");
|
||||||
var start = new ProcessStartInfo(executable, "--self-test")
|
var start = new ProcessStartInfo(executable, "--self-test")
|
||||||
{
|
{
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
@@ -464,22 +532,72 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
using (var process = Process.Start(start))
|
using (var process = Process.Start(start))
|
||||||
{
|
{
|
||||||
if (process == null) throw new InvalidOperationException("Self-Test konnte nicht gestartet werden.");
|
if (process == null) throw new InvalidOperationException("Self-Test konnte nicht gestartet werden.");
|
||||||
var output = process.StandardOutput.ReadToEnd();
|
var outputRead = process.StandardOutput.ReadToEndAsync();
|
||||||
var error = process.StandardError.ReadToEnd();
|
var errorRead = process.StandardError.ReadToEndAsync();
|
||||||
if (!process.WaitForExit(30000))
|
if (!process.WaitForExit(30000))
|
||||||
{
|
{
|
||||||
process.Kill();
|
process.Kill();
|
||||||
throw new InvalidOperationException("Self-Test hat das Zeitlimit ueberschritten.");
|
process.WaitForExit();
|
||||||
|
System.Threading.Tasks.Task.WaitAll(outputRead, errorRead);
|
||||||
|
var timeoutOutput = outputRead.Result;
|
||||||
|
var timeoutError = errorRead.Result;
|
||||||
|
setupLog.Write("ERROR", "Self-Test Timeout. label=" + label + ", stdout=" + timeoutOutput + ", stderr=" + timeoutError + ".");
|
||||||
|
throw new InvalidOperationException("Self-Test '" + label + "' hat das Zeitlimit von 30 Sekunden ueberschritten.");
|
||||||
|
}
|
||||||
|
System.Threading.Tasks.Task.WaitAll(outputRead, errorRead);
|
||||||
|
var output = outputRead.Result;
|
||||||
|
var error = errorRead.Result;
|
||||||
|
setupLog.Write(
|
||||||
|
"INFO",
|
||||||
|
"Self-Test beendet. label=" + label
|
||||||
|
+ ", exit_code=" + process.ExitCode
|
||||||
|
+ ", stdout=" + output
|
||||||
|
+ ", stderr=" + error + ".");
|
||||||
|
return ParseSelfTestOutput(output, error, process.ExitCode, expectedServiceCount, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IReadOnlyList<string> ParseSelfTestOutput(
|
||||||
|
string output,
|
||||||
|
string error,
|
||||||
|
int exitCode,
|
||||||
|
int? expectedServiceCount,
|
||||||
|
string label)
|
||||||
|
{
|
||||||
|
var lines = (output ?? string.Empty).Split(
|
||||||
|
new[] { "\r\n", "\n" },
|
||||||
|
StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var expected = expectedServiceCount.HasValue
|
||||||
|
? expectedServiceCount.Value.ToString(CultureInfo.InvariantCulture)
|
||||||
|
: "mindestens 1 (Legacy-kompatibel)";
|
||||||
|
if (exitCode != 0
|
||||||
|
|| lines.Length == 0
|
||||||
|
|| expectedServiceCount.HasValue && lines.Length != expectedServiceCount.Value
|
||||||
|
|| lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Self-Test '" + label + "' fehlgeschlagen. Exitcode=" + exitCode
|
||||||
|
+ ", Zeilen=" + lines.Length + ", Erwartet=" + expected
|
||||||
|
+ ", Stdout=" + CompactProcessText(output)
|
||||||
|
+ ", Stderr=" + CompactProcessText(error) + ".");
|
||||||
}
|
}
|
||||||
var lines = output.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
if (process.ExitCode != 0 || lines.Length != 9 || lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
|
|
||||||
throw new InvalidOperationException("Self-Test fehlgeschlagen. Exitcode=" + process.ExitCode + ", Zeilen=" + lines.Length + ". " + error);
|
|
||||||
|
|
||||||
var serviceNames = lines.Select(ExtractServiceName).ToArray();
|
var serviceNames = lines.Select(ExtractServiceName).ToArray();
|
||||||
if (serviceNames.Distinct(StringComparer.Ordinal).Count() != serviceNames.Length)
|
if (serviceNames.Distinct(StringComparer.Ordinal).Count() != serviceNames.Length)
|
||||||
throw new InvalidOperationException("Self-Test fehlgeschlagen: Checkmk-Servicenamen sind nicht eindeutig.");
|
throw new InvalidOperationException(
|
||||||
|
"Self-Test '" + label + "' fehlgeschlagen: Checkmk-Servicenamen sind nicht eindeutig. "
|
||||||
|
+ "Services=[" + string.Join(", ", serviceNames) + "].");
|
||||||
return serviceNames;
|
return serviceNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string CompactProcessText(string value)
|
||||||
|
{
|
||||||
|
var text = (value ?? string.Empty)
|
||||||
|
.Replace("\r", string.Empty)
|
||||||
|
.Replace("\n", " | ")
|
||||||
|
.Trim();
|
||||||
|
const int maxLength = 2000;
|
||||||
|
return text.Length <= maxLength ? text : text.Substring(0, maxLength) + "...[gekuerzt]";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string RunRuntimeValidation(
|
private static string RunRuntimeValidation(
|
||||||
@@ -501,13 +619,21 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
using (var process = Process.Start(start))
|
using (var process = Process.Start(start))
|
||||||
{
|
{
|
||||||
if (process == null) throw new InvalidOperationException("Runtime-Abnahme konnte nicht gestartet werden.");
|
if (process == null) throw new InvalidOperationException("Runtime-Abnahme konnte nicht gestartet werden.");
|
||||||
var output = process.StandardOutput.ReadToEnd().Trim();
|
var outputRead = process.StandardOutput.ReadToEndAsync();
|
||||||
var error = process.StandardError.ReadToEnd().Trim();
|
var errorRead = process.StandardError.ReadToEndAsync();
|
||||||
if (!process.WaitForExit(30000))
|
if (!process.WaitForExit(30000))
|
||||||
{
|
{
|
||||||
process.Kill();
|
process.Kill();
|
||||||
throw new TimeoutException("Runtime-Abnahme hat das Zeitlimit ueberschritten.");
|
process.WaitForExit();
|
||||||
|
System.Threading.Tasks.Task.WaitAll(outputRead, errorRead);
|
||||||
|
throw new TimeoutException(
|
||||||
|
"Runtime-Abnahme hat das Zeitlimit von 30 Sekunden ueberschritten. Stdout="
|
||||||
|
+ CompactProcessText(outputRead.Result)
|
||||||
|
+ ", Stderr=" + CompactProcessText(errorRead.Result) + ".");
|
||||||
}
|
}
|
||||||
|
System.Threading.Tasks.Task.WaitAll(outputRead, errorRead);
|
||||||
|
var output = outputRead.Result.Trim();
|
||||||
|
var error = errorRead.Result.Trim();
|
||||||
|
|
||||||
if (process.ExitCode != 0
|
if (process.ExitCode != 0
|
||||||
|| !output.StartsWith("RUNTIME_VALIDATION_V1 ", StringComparison.Ordinal))
|
|| !output.StartsWith("RUNTIME_VALIDATION_V1 ", StringComparison.Ordinal))
|
||||||
@@ -542,7 +668,10 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
var removed = installed.Except(staged, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
var removed = installed.Except(staged, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||||
var added = staged.Except(installed, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
var added = staged.Except(installed, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||||
var description = "Entfernt=[" + string.Join(", ", removed) + "]; Neu=[" + string.Join(", ", added) + "].";
|
var description = "Entfernt=[" + string.Join(", ", removed) + "]; Neu=[" + string.Join(", ", added) + "].";
|
||||||
if (!allowServiceNameChange)
|
// Reine Erweiterungen lassen bestehende Checkmk-Services unangetastet und
|
||||||
|
// muessen Legacy-Updates deshalb nicht blockieren. Entfernte Namen sind
|
||||||
|
// dagegen ein Rename oder ein Vertragsbruch und bleiben bestaetigungspflichtig.
|
||||||
|
if (removed.Length > 0 && !allowServiceNameChange)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"Sicherheitsstopp: Das Update wuerde Checkmk-Servicenamen aendern. "
|
"Sicherheitsstopp: Das Update wuerde Checkmk-Servicenamen aendern. "
|
||||||
@@ -553,5 +682,50 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
|
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void WriteRecentProviderLog(
|
||||||
|
SetupOperationLog setupLog,
|
||||||
|
string runtimeRoot,
|
||||||
|
DateTime operationStartedUtc)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var directory = Path.Combine(runtimeRoot, "logs");
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
setupLog.Write("INFO", "Provider-Logverzeichnis existiert nicht: " + directory + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var file = Directory.GetFiles(directory, "biztalk-checkmk-pulse-*.log")
|
||||||
|
.OrderByDescending(File.GetLastWriteTimeUtc)
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (file == null)
|
||||||
|
{
|
||||||
|
setupLog.Write("INFO", "Kein Provider-Log fuer die Fehlerdiagnose gefunden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastWriteUtc = File.GetLastWriteTimeUtc(file);
|
||||||
|
if (lastWriteUtc < operationStartedUtc.AddSeconds(-5))
|
||||||
|
{
|
||||||
|
setupLog.Write(
|
||||||
|
"WARN",
|
||||||
|
"Neuestes Provider-Log ist aelter als dieser Setup-Lauf und wird nicht als Fehlerkontext uebernommen. "
|
||||||
|
+ "path=" + file + ", last_write_utc=" + lastWriteUtc.ToString("o") + ".");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = File.ReadAllLines(file);
|
||||||
|
var tail = lines.Skip(Math.Max(0, lines.Length - 40));
|
||||||
|
setupLog.Write(
|
||||||
|
"ERROR",
|
||||||
|
"Letzte Provider-Logzeilen. path=" + file + ", tail=" + string.Join(" || ", tail) + ".");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
setupLog.Write("WARN", "Provider-Log konnte nicht fuer die Diagnose gelesen werden: " + ex.Message + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.4.0")]
|
[assembly: AssemblyVersion("2.2.5.0")]
|
||||||
[assembly: AssemblyFileVersion("2.2.4.0")]
|
[assembly: AssemblyFileVersion("2.2.5.0")]
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BizTalkCheckmkPulse.Setup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Best-effort-Diagnoselog fuer einen einzelnen Setup-Lauf. Das Kennwort
|
||||||
|
/// wird dieser Klasse nie uebergeben und kann deshalb nicht protokolliert werden.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SetupOperationLog : IDisposable
|
||||||
|
{
|
||||||
|
private readonly object sync = new object();
|
||||||
|
private StreamWriter writer;
|
||||||
|
|
||||||
|
private SetupOperationLog(string filePath, StreamWriter writer)
|
||||||
|
{
|
||||||
|
FilePath = filePath;
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FilePath { get; private set; }
|
||||||
|
|
||||||
|
public static SetupOperationLog Create(string runtimeDirectory, string operation)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var directory = Path.Combine(runtimeDirectory, "logs");
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
var path = Path.Combine(
|
||||||
|
directory,
|
||||||
|
"setup-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmssfff", CultureInfo.InvariantCulture)
|
||||||
|
+ "-" + Process.GetCurrentProcess().Id + ".log");
|
||||||
|
var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
|
||||||
|
var log = new SetupOperationLog(path, new StreamWriter(stream, new UTF8Encoding(false)) { AutoFlush = true });
|
||||||
|
log.Write("INFO", "Setup-Log gestartet. operation=" + operation
|
||||||
|
+ ", setup_version=" + typeof(SetupOperationLog).Assembly.GetName().Version
|
||||||
|
+ ", os=" + Environment.OSVersion.VersionString
|
||||||
|
+ ", process_bitness=" + (Environment.Is64BitProcess ? "64" : "32")
|
||||||
|
+ ", identity=" + CurrentIdentity() + ".");
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return new SetupOperationLog(string.Empty, null)
|
||||||
|
{
|
||||||
|
CreationError = ex.Message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string CreationError { get; private set; }
|
||||||
|
|
||||||
|
public void Write(string level, string message)
|
||||||
|
{
|
||||||
|
lock (sync)
|
||||||
|
{
|
||||||
|
if (writer == null) return;
|
||||||
|
writer.WriteLine(
|
||||||
|
DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture)
|
||||||
|
+ " level=" + level
|
||||||
|
+ " " + SingleLine(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteException(string phase, Exception exception)
|
||||||
|
{
|
||||||
|
Write("ERROR", "Fehler in Phase '" + phase + "'. " + FormatException(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string FormatException(Exception exception)
|
||||||
|
{
|
||||||
|
var result = new StringBuilder();
|
||||||
|
var current = exception;
|
||||||
|
var depth = 0;
|
||||||
|
while (current != null && depth < 10)
|
||||||
|
{
|
||||||
|
if (depth > 0) result.Append(" | inner[").Append(depth).Append("] ");
|
||||||
|
result.Append(current.GetType().FullName)
|
||||||
|
.Append(" hresult=0x")
|
||||||
|
.Append(current.HResult.ToString("X8", CultureInfo.InvariantCulture))
|
||||||
|
.Append(": ")
|
||||||
|
.Append(current.Message);
|
||||||
|
if (!string.IsNullOrWhiteSpace(current.StackTrace))
|
||||||
|
result.Append(" stack=").Append(current.StackTrace);
|
||||||
|
current = current.InnerException;
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
return SingleLine(result.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (sync)
|
||||||
|
{
|
||||||
|
if (writer == null) return;
|
||||||
|
writer.Dispose();
|
||||||
|
writer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CurrentIdentity()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var identity = WindowsIdentity.GetCurrent();
|
||||||
|
return identity == null ? "(unknown)" : identity.Name;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return "(unknown)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SingleLine(string value)
|
||||||
|
{
|
||||||
|
return (value ?? string.Empty)
|
||||||
|
.Replace("\r", "\\r")
|
||||||
|
.Replace("\n", "\\n")
|
||||||
|
.Replace("\t", " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Globalization;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
private const int TaskTriggerTime = 1;
|
private const int TaskTriggerTime = 1;
|
||||||
private const int TaskActionExecute = 0;
|
private const int TaskActionExecute = 0;
|
||||||
private const int TaskInstancesIgnoreNew = 2;
|
private const int TaskInstancesIgnoreNew = 2;
|
||||||
|
private const int TaskStateReady = 3;
|
||||||
private const int TaskStateRunning = 4;
|
private const int TaskStateRunning = 4;
|
||||||
|
|
||||||
public void RegisterValidationAndStart(
|
public void RegisterValidationAndStart(
|
||||||
@@ -165,11 +168,20 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
return new TaskRunResult(lastRunUtc.Value, lastResult);
|
return new TaskRunResult(lastRunUtc.Value, lastResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsFreshCompletedFailure(lastState, lastResult, lastRunUtc, notBeforeUtc))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Providerlauf ist fehlgeschlagen. state=" + DescribeState(lastState)
|
||||||
|
+ ", last_result=" + DescribeResult(lastResult)
|
||||||
|
+ ", last_run=" + lastRunUtc.Value.ToString("o")
|
||||||
|
+ ". Provider- und Setup-Log pruefen.");
|
||||||
|
}
|
||||||
|
|
||||||
if (report != null && DateTime.UtcNow >= nextReportUtc)
|
if (report != null && DateTime.UtcNow >= nextReportUtc)
|
||||||
{
|
{
|
||||||
report(
|
report(
|
||||||
"Warte auf erfolgreichen Providerlauf: state=" + lastState
|
"Warte auf erfolgreichen Providerlauf: state=" + DescribeState(lastState)
|
||||||
+ ", last_result=" + lastResult
|
+ ", last_result=" + DescribeResult(lastResult)
|
||||||
+ ", last_run=" + (lastRunUtc.HasValue ? lastRunUtc.Value.ToString("o") : "(noch keiner)") + ".");
|
+ ", last_run=" + (lastRunUtc.HasValue ? lastRunUtc.Value.ToString("o") : "(noch keiner)") + ".");
|
||||||
nextReportUtc = DateTime.UtcNow.AddSeconds(15);
|
nextReportUtc = DateTime.UtcNow.AddSeconds(15);
|
||||||
}
|
}
|
||||||
@@ -179,8 +191,8 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
|
|
||||||
throw new TimeoutException(
|
throw new TimeoutException(
|
||||||
"Providerlauf wurde innerhalb von " + Math.Ceiling(timeout.TotalSeconds)
|
"Providerlauf wurde innerhalb von " + Math.Ceiling(timeout.TotalSeconds)
|
||||||
+ " Sekunden nicht erfolgreich abgeschlossen. state=" + lastState
|
+ " Sekunden nicht erfolgreich abgeschlossen. state=" + DescribeState(lastState)
|
||||||
+ ", last_result=" + lastResult
|
+ ", last_result=" + DescribeResult(lastResult)
|
||||||
+ ", last_run=" + (lastRunUtc.HasValue ? lastRunUtc.Value.ToString("o") : "(noch keiner)") + ".");
|
+ ", last_run=" + (lastRunUtc.HasValue ? lastRunUtc.Value.ToString("o") : "(noch keiner)") + ".");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -197,13 +209,77 @@ namespace BizTalkCheckmkPulse.Setup
|
|||||||
DateTime? lastRunUtc,
|
DateTime? lastRunUtc,
|
||||||
DateTime notBeforeUtc)
|
DateTime notBeforeUtc)
|
||||||
{
|
{
|
||||||
return taskState != TaskStateRunning
|
return taskState == TaskStateReady
|
||||||
&& lastTaskResult == 0
|
&& lastTaskResult == 0
|
||||||
&& lastRunUtc.HasValue
|
&& lastRunUtc.HasValue
|
||||||
// Task Scheduler kann LastRunTime mit geringerer Aufloesung als DateTime.UtcNow liefern.
|
// Task Scheduler kann LastRunTime mit geringerer Aufloesung als DateTime.UtcNow liefern.
|
||||||
&& lastRunUtc.Value >= notBeforeUtc.ToUniversalTime().AddSeconds(-2);
|
&& lastRunUtc.Value >= notBeforeUtc.ToUniversalTime().AddSeconds(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool IsFreshCompletedFailure(
|
||||||
|
int taskState,
|
||||||
|
int lastTaskResult,
|
||||||
|
DateTime? lastRunUtc,
|
||||||
|
DateTime notBeforeUtc)
|
||||||
|
{
|
||||||
|
return taskState == TaskStateReady
|
||||||
|
&& lastTaskResult != 0
|
||||||
|
&& lastRunUtc.HasValue
|
||||||
|
&& lastRunUtc.Value >= notBeforeUtc.ToUniversalTime().AddSeconds(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string DescribeResult(int result)
|
||||||
|
{
|
||||||
|
var unsigned = unchecked((uint)result);
|
||||||
|
string description;
|
||||||
|
switch (unsigned)
|
||||||
|
{
|
||||||
|
case 0: description = "Erfolgreich"; break;
|
||||||
|
case 1: description = "Provider-Laufzeitfehler; Provider-Log pruefen"; break;
|
||||||
|
case 2: description = "Provider bereits aktiv oder Snapshot-I/O fehlgeschlagen"; break;
|
||||||
|
case 0x00041300: description = "Task ist bereit"; break;
|
||||||
|
case 0x00041301: description = "Task wird ausgefuehrt"; break;
|
||||||
|
case 0x00041302: description = "Task ist deaktiviert"; break;
|
||||||
|
case 0x00041303: description = "Task wurde noch nicht ausgefuehrt"; break;
|
||||||
|
case 0x00041306: description = "Letzter Tasklauf wurde beendet"; break;
|
||||||
|
default:
|
||||||
|
if ((unsigned & 0xFFFF0000u) == 0x80070000u)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
description = new Win32Exception((int)(unsigned & 0xFFFFu)).Message;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
description = "Windows-/Task-Scheduler-Fehler";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
description = "Anwendungs- oder Task-Scheduler-Code";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result.ToString(CultureInfo.InvariantCulture)
|
||||||
|
+ " (0x" + unsigned.ToString("X8", CultureInfo.InvariantCulture) + ")"
|
||||||
|
+ " '" + description + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DescribeState(int state)
|
||||||
|
{
|
||||||
|
string name;
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case 0: name = "Unknown"; break;
|
||||||
|
case 1: name = "Disabled"; break;
|
||||||
|
case 2: name = "Queued"; break;
|
||||||
|
case 3: name = "Ready"; break;
|
||||||
|
case 4: name = "Running"; break;
|
||||||
|
default: name = "Unbekannt"; break;
|
||||||
|
}
|
||||||
|
return state.ToString(CultureInfo.InvariantCulture) + " (" + name + ")";
|
||||||
|
}
|
||||||
|
|
||||||
public void DeleteIfExists(string taskName)
|
public void DeleteIfExists(string taskName)
|
||||||
{
|
{
|
||||||
dynamic service = null;
|
dynamic service = null;
|
||||||
|
|||||||
@@ -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.4.0")]
|
[assembly: AssemblyVersion("2.2.5.0")]
|
||||||
[assembly: AssemblyFileVersion("2.2.4.0")]
|
[assembly: AssemblyFileVersion("2.2.5.0")]
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Run("DefaultServiceContractIsExact", DefaultServiceContractIsExact);
|
Run("DefaultServiceContractIsExact", DefaultServiceContractIsExact);
|
||||||
Run("InstallerRejectsUnconfirmedServiceRename", InstallerRejectsUnconfirmedServiceRename);
|
Run("InstallerRejectsUnconfirmedServiceRename", InstallerRejectsUnconfirmedServiceRename);
|
||||||
Run("InstallerAllowsConfirmedServiceRename", InstallerAllowsConfirmedServiceRename);
|
Run("InstallerAllowsConfirmedServiceRename", InstallerAllowsConfirmedServiceRename);
|
||||||
|
Run("InstallerAcceptsLegacyEightServiceSelfTest", InstallerAcceptsLegacyEightServiceSelfTest);
|
||||||
|
Run("InstallerRequiresNineServicesForCandidate", InstallerRequiresNineServicesForCandidate);
|
||||||
|
Run("InstallerAllowsAdditiveServiceContract", InstallerAllowsAdditiveServiceContract);
|
||||||
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
|
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
|
||||||
Run("RuntimeValidationAcceptsFreshConsistentArtifacts", RuntimeValidationAcceptsFreshConsistentArtifacts);
|
Run("RuntimeValidationAcceptsFreshConsistentArtifacts", RuntimeValidationAcceptsFreshConsistentArtifacts);
|
||||||
Run("RuntimeValidationRejectsPreInstallSnapshot", RuntimeValidationRejectsPreInstallSnapshot);
|
Run("RuntimeValidationRejectsPreInstallSnapshot", RuntimeValidationRejectsPreInstallSnapshot);
|
||||||
@@ -53,6 +56,7 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Run("ForceEndpointRefreshRequiresProviderMode", ForceEndpointRefreshRequiresProviderMode);
|
Run("ForceEndpointRefreshRequiresProviderMode", ForceEndpointRefreshRequiresProviderMode);
|
||||||
Run("RuntimeValidationArgumentsAreParsedStrictly", RuntimeValidationArgumentsAreParsedStrictly);
|
Run("RuntimeValidationArgumentsAreParsedStrictly", RuntimeValidationArgumentsAreParsedStrictly);
|
||||||
Run("TaskCompletionRequiresFreshSuccessfulRun", TaskCompletionRequiresFreshSuccessfulRun);
|
Run("TaskCompletionRequiresFreshSuccessfulRun", TaskCompletionRequiresFreshSuccessfulRun);
|
||||||
|
Run("TaskFailureIsDetectedImmediately", TaskFailureIsDetectedImmediately);
|
||||||
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
||||||
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
||||||
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
||||||
@@ -810,6 +814,51 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Assert(change.Contains("BizTalk Platform"), "confirmed rename must report new service");
|
Assert(change.Contains("BizTalk Platform"), "confirmed rename must report new service");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void InstallerAcceptsLegacyEightServiceSelfTest()
|
||||||
|
{
|
||||||
|
var output = string.Join("\r\n", Enumerable.Range(1, 8)
|
||||||
|
.Select(x => "0 \"BizTalk Legacy " + x + "\" - Self test OK"));
|
||||||
|
var services = BizTalkCheckmkPulse.Setup.InstallerEngine.ParseSelfTestOutput(
|
||||||
|
output,
|
||||||
|
string.Empty,
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
"Installierte Version");
|
||||||
|
AssertEqual(8, services.Count, "legacy self-test service count");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InstallerRequiresNineServicesForCandidate()
|
||||||
|
{
|
||||||
|
var output = string.Join("\n", Enumerable.Range(1, 8)
|
||||||
|
.Select(x => "0 \"BizTalk Candidate " + x + "\" - Self test OK"));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BizTalkCheckmkPulse.Setup.InstallerEngine.ParseSelfTestOutput(
|
||||||
|
output,
|
||||||
|
string.Empty,
|
||||||
|
0,
|
||||||
|
9,
|
||||||
|
"Staging");
|
||||||
|
throw new InvalidOperationException("candidate with eight services was accepted");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
Assert(ex.Message.Contains("Zeilen=8"), "candidate failure must report actual line count");
|
||||||
|
Assert(ex.Message.Contains("Erwartet=9"), "candidate failure must report expected line count");
|
||||||
|
Assert(ex.Message.Contains("Stdout="), "candidate failure must include stdout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InstallerAllowsAdditiveServiceContract()
|
||||||
|
{
|
||||||
|
var change = BizTalkCheckmkPulse.Setup.InstallerEngine.EnsureServiceNameCompatibility(
|
||||||
|
new[] { "BizTalk Platform", "BizTalk Event Log" },
|
||||||
|
new[] { "BizTalk Platform", "BizTalk Endpoint Reachability", "BizTalk Event Log" },
|
||||||
|
false);
|
||||||
|
Assert(change.Contains("Entfernt=[]"), "additive contract must not remove a service");
|
||||||
|
Assert(change.Contains("BizTalk Endpoint Reachability"), "additive contract must report the new service");
|
||||||
|
}
|
||||||
|
|
||||||
private static string ExtractQuotedServiceName(string line)
|
private static string ExtractQuotedServiceName(string line)
|
||||||
{
|
{
|
||||||
var start = line.IndexOf('"') + 1;
|
var start = line.IndexOf('"') + 1;
|
||||||
@@ -1030,6 +1079,19 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
3, 0, boundary.AddSeconds(-3), boundary), "old successful task must not pass");
|
3, 0, boundary.AddSeconds(-3), boundary), "old successful task must not pass");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void TaskFailureIsDetectedImmediately()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow;
|
||||||
|
Assert(BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsFreshCompletedFailure(
|
||||||
|
3, 8, boundary.AddSeconds(1), boundary), "fresh failed task must be detected");
|
||||||
|
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsFreshCompletedFailure(
|
||||||
|
4, 8, boundary.AddSeconds(1), boundary), "running task must not be treated as completed failure");
|
||||||
|
Assert(!BizTalkCheckmkPulse.Setup.TaskSchedulerService.IsFreshCompletedFailure(
|
||||||
|
3, 8, boundary.AddSeconds(-3), boundary), "old task failure must not fail a new validation run");
|
||||||
|
var description = BizTalkCheckmkPulse.Setup.TaskSchedulerService.DescribeResult(8);
|
||||||
|
Assert(description.Contains("0x00000008"), "task result must include hexadecimal code");
|
||||||
|
}
|
||||||
|
|
||||||
private static RuntimeValidationResult ValidateRuntimeArtifacts(
|
private static RuntimeValidationResult ValidateRuntimeArtifacts(
|
||||||
DateTime boundaryUtc,
|
DateTime boundaryUtc,
|
||||||
DateTime snapshotUtc,
|
DateTime snapshotUtc,
|
||||||
|
|||||||
Reference in New Issue
Block a user