Compare commits
4
Commits
8fe1b57173
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f8e8034e6 | ||
|
|
951a5e72d1 | ||
|
|
cc1e59916a | ||
|
|
448be8dc49 |
@@ -408,6 +408,54 @@ 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 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Version 2.2.6 behandelt einen fachlichen UNKNOWN-Zustand der Endpoint-Prüfung
|
||||||
|
nicht mehr als Installationsdefekt. Sind Providerlauf, Snapshot, Identität,
|
||||||
|
neun Services und der erzwungen aktualisierte Katalog technisch valide, darf
|
||||||
|
`BizTalk Endpoint Reachability` als einziger Service `UNKNOWN` bleiben. Die
|
||||||
|
vollständige Local-Check-Zeile wird als Abnahmewarnung ausgegeben und bleibt
|
||||||
|
unverändert für Checkmk erhalten. Damit wird eine unvollständige
|
||||||
|
Endpoint-Auflösung nicht grüngefärbt und zugleich kein funktionsfähiges Update
|
||||||
|
zurückgerollt. UNKNOWN in Platform, SQL Access, Suspensions, Host Instances,
|
||||||
|
Receive Locations, Send Ports, Orchestrations oder Event Log bleibt
|
||||||
|
blockierend, weil dort die Kern-Datenerhebung nicht zuverlässig belegt ist.
|
||||||
|
|
||||||
|
Version 2.2.7 klassifiziert die im RTM-Abnahmelauf identifizierten Adapter
|
||||||
|
präziser. Scheduling-Komponenten sind keine Netzwerkziele und werden komplett
|
||||||
|
ausgenommen. `Database://host[:port]/.../Oracle` wird per TCP auf den
|
||||||
|
expliziten Port oder den Oracle-Standard 1521 geprüft. WCF-SAP verwendet den
|
||||||
|
ersten SAProuter-Hop oder das Paar `ListenerGwHost`/`ListenerGwServ`; dabei
|
||||||
|
entspricht `sapgwNN` dem TCP-Port `33NN`. Diese beiden Adapterklassen laufen
|
||||||
|
als Best-Effort-Diagnose: Ein Fehlschlag bleibt über Metriken sichtbar, erzeugt
|
||||||
|
aber keinen Alarm. Alle regulären und manuellen Ziele behalten ihre bisherige
|
||||||
|
strenge Statuslogik.
|
||||||
|
|
||||||
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 +598,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 +652,12 @@ 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, Kernservice-`UNKNOWN`, alten/ungültigen Katalog und
|
||||||
|
fehlerhaften oder nicht rechtzeitig beendeten Tasklauf zurück; ein isoliertes
|
||||||
|
Endpoint-Reachability-`UNKNOWN` wird mit vollständigem Detail akzeptiert,
|
||||||
- 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,
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ Checkmk-Service `BizTalk Endpoint Reachability` zusammengefasst:
|
|||||||
- HTTP(S), SFTP, FTP, WCF/`net.tcp` und UNC/SMB werden durch einen reinen
|
- HTTP(S), SFTP, FTP, WCF/`net.tcp` und UNC/SMB werden durch einen reinen
|
||||||
TCP-Verbindungsaufbau zu Host und Port geprüft. Explizite
|
TCP-Verbindungsaufbau zu Host und Port geprüft. Explizite
|
||||||
`udp://host:port`-Ziele verwenden ein UDP-Datagramm.
|
`udp://host:port`-Ziele verwenden ein UDP-Datagramm.
|
||||||
|
- Scheduling-Komponenten werden vollständig aus der Endpoint-Prüfung
|
||||||
|
ausgeschlossen. Oracle-`DATABASE`-Ziele werden aus
|
||||||
|
`Database://host[:port]/.../Oracle` auf TCP 1521 beziehungsweise den
|
||||||
|
expliziten Port reduziert. WCF-SAP nutzt den ersten SAProuter-Hop oder
|
||||||
|
`ListenerGwHost`/`ListenerGwServ`; `sapgwNN` wird als TCP `33NN` aufgelöst.
|
||||||
|
- Automatisch abgeleitete DATABASE-/WCF-SAP-Probes sind Best-Effort:
|
||||||
|
erfolgreiche Tests bleiben messbar, nicht ableitbare oder nicht erreichbare
|
||||||
|
Ziele erzeugen jedoch weder `UNKNOWN` noch `WARN`/`CRIT`. Für verbindliche
|
||||||
|
Alarmierung kann weiterhin ein manueller Katalogeintrag verwendet werden.
|
||||||
- Es werden weder HTTP-Aufrufe noch Anmeldungen, fachliche Requests oder
|
- Es werden weder HTTP-Aufrufe noch Anmeldungen, fachliche Requests oder
|
||||||
BizTalk-Nachrichten gesendet. Der Check validiert DNS, Netzwerkpfad,
|
BizTalk-Nachrichten gesendet. Der Check validiert DNS, Netzwerkpfad,
|
||||||
Firewall und bei TCP einen annehmenden Zielport, nicht die fachliche
|
Firewall und bei TCP einen annehmenden Zielport, nicht die fachliche
|
||||||
@@ -84,6 +93,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.
|
||||||
|
|
||||||
@@ -124,11 +137,13 @@ Auch schemafreie `host:port`-Angaben sowie FTP-/SFTP-Adressen der Form
|
|||||||
nicht in den Endpoint-Katalog übernommen.
|
nicht in den Endpoint-Katalog übernommen.
|
||||||
|
|
||||||
Nicht jede aktive BizTalk-Adresse ist selbst ein Socket-Ziel. Dynamische Send
|
Nicht jede aktive BizTalk-Adresse ist selbst ein Socket-Ziel. Dynamische Send
|
||||||
Ports, lokale Pfade, SMTP-Empfänger, Named Pipes und relative HTTP-/WCF-
|
Ports, lokale Pfade, SMTP-Empfänger, Named Pipes, relative HTTP-/WCF-
|
||||||
Receive-Listener werden deshalb als erwartbar nicht socket-prüfbar behandelt
|
Receive-Listener, Scheduling-Komponenten sowie nicht sicher ableitbare
|
||||||
und erzeugen kein `UNKNOWN`. Nur eine aktive statische Adresse, die tatsächlich
|
DATABASE-/WCF-SAP-Adressen werden deshalb als erwartbar nicht socket-prüfbar
|
||||||
ein Netzwerkziel enthalten müsste und nicht sicher aufgelöst werden kann,
|
behandelt und erzeugen kein `UNKNOWN`. Nur eine andere aktive statische
|
||||||
bleibt `UNKNOWN` und wird geheimnisfrei mit Artefakt, Adapter und Grund genannt.
|
Adresse, die tatsächlich ein Netzwerkziel enthalten müsste und nicht sicher
|
||||||
|
aufgelöst werden kann, bleibt `UNKNOWN` und wird geheimnisfrei mit Artefakt,
|
||||||
|
Adapter und Grund genannt.
|
||||||
|
|
||||||
Der geheimnisfreie Katalog liegt unter
|
Der geheimnisfreie Katalog liegt unter
|
||||||
`%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml`. Fehlt er, wird er beim
|
`%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml`. Fehlt er, wird er beim
|
||||||
@@ -141,8 +156,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)
|
||||||
|
|
||||||
@@ -151,6 +169,10 @@ Beispielausgaben: [docs/ExampleOutput.md](docs/ExampleOutput.md)
|
|||||||
Endpoint-Katalog, manuelle Overrides und Protokollgrenzen:
|
Endpoint-Katalog, manuelle Overrides und Protokollgrenzen:
|
||||||
[docs/EndpointCatalog.md](docs/EndpointCatalog.md)
|
[docs/EndpointCatalog.md](docs/EndpointCatalog.md)
|
||||||
|
|
||||||
|
Die RTM-Adapterregeln und ihre Herstellerquellen sind in
|
||||||
|
[docs/RTM-Endpoint-Adapterregeln-2026-08-11.md](docs/RTM-Endpoint-Adapterregeln-2026-08-11.md)
|
||||||
|
festgehalten.
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
Build-Host:
|
Build-Host:
|
||||||
@@ -217,7 +239,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.
|
||||||
|
|
||||||
@@ -258,24 +282,42 @@ 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.6):
|
||||||
|
|
||||||
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. schaltet die Programmdateien per Verzeichniswechsel um; bei einem Fehler
|
3. vergleicht installierten und neuen Checkmk-Servicevertrag. Rein additive
|
||||||
werden vorherige Version, Wrapper und Task best effort wiederhergestellt,
|
Services werden zugelassen und als Discovery-Hinweis gemeldet; entfernte
|
||||||
4. behält `%ProgramData%\BizTalkCheckmkPulse` mit Endpoint-Katalog, Snapshot
|
oder umbenannte Services stoppen ohne ausdrückliche Bestätigung vor jeder
|
||||||
und Logs bei,
|
Umschaltung,
|
||||||
5. installiert EXE und Config nach `%ProgramFiles%\BizTalkCheckmkPulse`,
|
4. schaltet die Programmdateien per Verzeichniswechsel um,
|
||||||
6. setzt explizite ACLs für Administratoren, Provider und `LocalSystem`,
|
5. startet einen triggerlosen Provider-Abnahmelauf unter dem echten
|
||||||
7. installiert nur den kleinen `.cmd`-Consumer unter
|
Collector-Konto und erzwingt einen vollständigen Endpoint-Katalogabgleich,
|
||||||
|
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,
|
||||||
|
die Collector-Identität, alle neun eindeutigen Services sowie den frischen,
|
||||||
|
maschinen- und umgebungsgebundenen Endpoint-Katalog. `UNKNOWN` in einem
|
||||||
|
Kernservice bleibt ein Abnahmefehler. Ein ausschließlich bei
|
||||||
|
`BizTalk Endpoint Reachability` auftretendes `UNKNOWN` wird als sichtbare
|
||||||
|
Betriebswarnung übernommen, weil es eine unvollständige Prüfabdeckung und
|
||||||
|
keinen defekten Installationslauf beschreibt,
|
||||||
|
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. 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
|
||||||
|
explizite ACLs für Administratoren, Provider und `LocalSystem` und
|
||||||
|
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.
|
||||||
|
|
||||||
@@ -292,8 +334,20 @@ 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.
|
||||||
|
|
||||||
|
Der nachfolgende ACC-Abbruch mit einem technisch validen Snapshot und
|
||||||
|
`UNKNOWN=[BizTalk Endpoint Reachability]` ist in
|
||||||
|
[docs/Installer-Runtime-Unknown-Analyse-2026-08-11.md](docs/Installer-Runtime-Unknown-Analyse-2026-08-11.md)
|
||||||
|
analysiert. Der Zustand wird nicht grüngefärbt: Setup 2.2.6 schließt ab und
|
||||||
|
gibt die vollständige UNKNOWN-Zeile als Warnung aus; Checkmk behält Status 3.
|
||||||
|
|
||||||
Snapshot und Consumer:
|
Snapshot und Consumer:
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
@@ -356,6 +410,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. |
|
||||||
@@ -395,7 +450,7 @@ liest den nächsten atomar publizierten Snapshot.
|
|||||||
| Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` prüfen. |
|
| Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` prüfen. |
|
||||||
| Receive Locations / Send Ports sind CRIT | `affected=` prüfen; nur fachlich bewusst inaktive Namen exakt in die jeweilige Allowlist aufnehmen. |
|
| Receive Locations / Send Ports sind CRIT | `affected=` prüfen; nur fachlich bewusst inaktive Namen exakt in die jeweilige Allowlist aufnehmen. |
|
||||||
| Endpoint Reachability ist CRIT | Nur `unavailable=` prüfen; DNS, Zielport, Firewall und externen Dienst kontrollieren. |
|
| Endpoint Reachability ist CRIT | Nur `unavailable=` prüfen; DNS, Zielport, Firewall und externen Dienst kontrollieren. |
|
||||||
| Endpoint Reachability ist UNKNOWN | `unresolved_endpoints=`, WMI-Vollständigkeit, `endpoints.xml` und wöchentlichen Refresh prüfen; indirekte Adapterziele bei Bedarf geheimnisfrei manuell überschreiben. |
|
| Endpoint Reachability ist UNKNOWN | `unresolved_endpoints=`, WMI-Vollständigkeit, `endpoints.xml` und wöchentlichen Refresh prüfen. Schedule sowie nicht ableitbare DATABASE-/WCF-SAP-Ziele sind ab 2.2.7 bereits ausgenommen; andere indirekte Ziele bei Bedarf geheimnisfrei manuell überschreiben. |
|
||||||
| `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema prüfen; keine Rechte ausweiten. |
|
| `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema prüfen; keine Rechte ausweiten. |
|
||||||
| Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos prüfen. |
|
| Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos prüfen. |
|
||||||
| Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL prüfen. |
|
| Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL prüfen. |
|
||||||
|
|||||||
@@ -28,14 +28,72 @@ 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.6 trennt technische Installationsabnahme und fachlichen
|
||||||
|
Endpoint-Zustand. Ein frischer, integritäts- und identitätsgeprüfter Snapshot
|
||||||
|
mit allen neun Services und ein frisch synchronisierter Endpoint-Katalog
|
||||||
|
belegen eine funktionsfähige Installation. Meldet ausschließlich
|
||||||
|
`BizTalk Endpoint Reachability` weiterhin `UNKNOWN`, wird die Installation
|
||||||
|
abgeschlossen und die vollständige Checkmk-Zeile als Warnung im Setup sowie
|
||||||
|
im Setup-Log ausgegeben. Der Status bleibt im Snapshot und in Checkmk
|
||||||
|
unverändert `UNKNOWN`, bis Auflösungslücken, Katalog-/Probeprobleme oder
|
||||||
|
fehlende manuelle Overrides betrieblich geklärt sind. `UNKNOWN` in Platform,
|
||||||
|
SQL oder einem anderen Kernservice bleibt ein blockierender Abnahmefehler.
|
||||||
|
|
||||||
|
Version 2.2.7 beseitigt die bekannten fachlichen Fehlalarme der
|
||||||
|
Endpoint-Prüfung: Scheduling-Komponenten werden ausgeschlossen; Oracle-
|
||||||
|
DATABASE- und WCF-SAP-Ziele werden als TCP-Best-Effort-Probes abgeleitet.
|
||||||
|
Nicht ableitbare oder nicht erreichbare Best-Effort-Ziele lösen weder UNKNOWN
|
||||||
|
noch CRIT aus. Reguläre und manuell konfigurierte Ziele bleiben unverändert
|
||||||
|
streng alarmierend.
|
||||||
|
|
||||||
|
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` in Kernservices blockiert die
|
||||||
|
Installation; ausschließlich `BizTalk Endpoint Reachability = UNKNOWN`
|
||||||
|
wird ab 2.2.6 als nicht blockierende Betriebswarnung übernommen,
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# ACC-Analyse: Checkmk-Datenalter und Endpoint Reachability
|
# ACC-Analyse: Checkmk-Datenalter und Endpoint Reachability
|
||||||
|
|
||||||
|
> Nachtrag 11.08.2026: Version 2.2.7 behebt die aus der RTM-Ausgabe
|
||||||
|
> identifizierten Schedule-, Oracle-DATABASE- und WCF-SAP-Sonderfälle. Die
|
||||||
|
> verbindliche Nachfolgeregel steht in
|
||||||
|
> [RTM-Endpoint-Adapterregeln-2026-08-11.md](RTM-Endpoint-Adapterregeln-2026-08-11.md).
|
||||||
|
|
||||||
## Befund
|
## Befund
|
||||||
|
|
||||||
Die am 10.08.2026 bereitgestellten ACC-Artefakte zeigen zwei getrennte
|
Die am 10.08.2026 bereitgestellten ACC-Artefakte zeigen zwei getrennte
|
||||||
@@ -75,6 +80,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 +144,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.6 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.
@@ -185,6 +185,10 @@ Zweck:
|
|||||||
- Erkennt unter anderem Standard-URIs, UNC, MSMQ, `net.tcp`, WCF-SQL-
|
- Erkennt unter anderem Standard-URIs, UNC, MSMQ, `net.tcp`, WCF-SQL-
|
||||||
`mssql`-URIs, schemafreie `host:port`-Angaben, FTP-/SFTP-Ziele mit optionalen
|
`mssql`-URIs, schemafreie `host:port`-Angaben, FTP-/SFTP-Ziele mit optionalen
|
||||||
Benutzerinformationen sowie lokale HTTP-/WCF-Wildcard-Listener.
|
Benutzerinformationen sowie lokale HTTP-/WCF-Wildcard-Listener.
|
||||||
|
- Schließt Scheduling-Komponenten vollständig aus. Oracle-`DATABASE`- und
|
||||||
|
WCF-SAP-Adressen werden, soweit eindeutig möglich, als TCP-Best-Effort-Ziel
|
||||||
|
geprüft; fehlende Ableitung und fehlgeschlagene Best-Effort-Probes alarmieren
|
||||||
|
nicht.
|
||||||
- Behandelt Adressen ohne eigenständiges Socket-Ziel (zum Beispiel lokale
|
- Behandelt Adressen ohne eigenständiges Socket-Ziel (zum Beispiel lokale
|
||||||
Pfade, SMTP-Empfänger, Named Pipes und relative Receive-Listener) als
|
Pfade, SMTP-Empfänger, Named Pipes und relative Receive-Listener) als
|
||||||
erwartbar ausgenommen und nicht als Abdeckungsfehler.
|
erwartbar ausgenommen und nicht als Abdeckungsfehler.
|
||||||
@@ -197,8 +201,8 @@ Status:
|
|||||||
|
|
||||||
| Status | Bedingung |
|
| Status | Bedingung |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `OK` | Katalog und Runtimezustand vollständig; alle aktiven prüfbaren Ziele erreichbar. |
|
| `OK` | Katalog und Runtimezustand vollständig; alle alarmierenden Ziele erreichbar. Best-Effort-Fehler bei DATABASE/WCF-SAP sind höchstens als Metrik sichtbar. |
|
||||||
| `CRIT` | Mindestens ein getestetes TCP-/UDP-Ziel nicht erreichbar. |
|
| `CRIT` | Mindestens ein reguläres oder manuell konfiguriertes TCP-/UDP-Ziel nicht erreichbar. |
|
||||||
| `UNKNOWN` | Katalog fehlt/ist ungültig, Wochenabgleich scheitert, Runtimezustand ist unvollständig oder ein aktiver externer Endpoint kann nicht sicher auf Host/Port reduziert werden. |
|
| `UNKNOWN` | Katalog fehlt/ist ungültig, Wochenabgleich scheitert, Runtimezustand ist unvollständig oder ein aktiver externer Endpoint kann nicht sicher auf Host/Port reduziert werden. |
|
||||||
| `OK` mit Hinweis | Nur wenn `ProbeEndpointConnectivity=false`; der Test ist bewusst deaktiviert. |
|
| `OK` mit Hinweis | Nur wenn `ProbeEndpointConnectivity=false`; der Test ist bewusst deaktiviert. |
|
||||||
|
|
||||||
@@ -214,6 +218,8 @@ Metriken:
|
|||||||
| `biztalk_endpoints_failed` | Nicht erreichbare aktive Artefaktziele; CRIT ab 1. |
|
| `biztalk_endpoints_failed` | Nicht erreichbare aktive Artefaktziele; CRIT ab 1. |
|
||||||
| `biztalk_endpoints_unresolved` | Aktive statische Netzwerkadressen ohne sicher bestimmbaren Host/Port; `UNKNOWN` ab 1, sofern kein manueller Override existiert. |
|
| `biztalk_endpoints_unresolved` | Aktive statische Netzwerkadressen ohne sicher bestimmbaren Host/Port; `UNKNOWN` ab 1, sofern kein manueller Override existiert. |
|
||||||
| `biztalk_endpoints_expected_non_socket` | Aktive Artefakte, deren Adresse erwartbar kein eigenständiges TCP-/UDP-Ziel enthält. |
|
| `biztalk_endpoints_expected_non_socket` | Aktive Artefakte, deren Adresse erwartbar kein eigenständiges TCP-/UDP-Ziel enthält. |
|
||||||
|
| `biztalk_endpoints_best_effort` | Automatisch abgeleitete aktive Oracle-DATABASE-/WCF-SAP-Ziele. |
|
||||||
|
| `biztalk_endpoints_best_effort_ignored` | Fehlgeschlagene Best-Effort-Probes, die vereinbarungsgemäß keinen Alarm auslösen. |
|
||||||
| `biztalk_endpoints_manual_overrides` | Aktive Ziele, die bewusst über einen geheimnisfreien manuellen Katalogeintrag aufgelöst werden. |
|
| `biztalk_endpoints_manual_overrides` | Aktive Ziele, die bewusst über einen geheimnisfreien manuellen Katalogeintrag aufgelöst werden. |
|
||||||
| `biztalk_endpoints_inactive_skipped` | Konfigurierte Einträge, deren BizTalk-Artefakt aktuell nicht aktiv ist. |
|
| `biztalk_endpoints_inactive_skipped` | Konfigurierte Einträge, deren BizTalk-Artefakt aktuell nicht aktiv ist. |
|
||||||
| `biztalk_endpoint_probe_duration_ms` | Gesamtlaufzeit der parallelisierten Socket-Probes in Millisekunden. |
|
| `biztalk_endpoint_probe_duration_ms` | Gesamtlaufzeit der parallelisierten Socket-Probes in Millisekunden. |
|
||||||
|
|||||||
+27
-1
@@ -15,12 +15,26 @@ 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.
|
||||||
|
|
||||||
|
Ab Setup 2.2.6 blockiert ein technisch gültiger Abnahmelauf nicht mehr allein
|
||||||
|
deshalb, weil `BizTalk Endpoint Reachability` fachlich `UNKNOWN` meldet. Die
|
||||||
|
Installation wird mit einer sichtbaren Warnung abgeschlossen; Status, Metriken
|
||||||
|
und Detailtext bleiben im Snapshot erhalten. Ein fehlender, ungültiger oder
|
||||||
|
nicht in diesem Abnahmelauf synchronisierter Katalog bleibt dagegen ein harter
|
||||||
|
Installationsfehler.
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
- Receive Location nur bei `IsDisabled=false`
|
- Receive Location nur bei `IsDisabled=false`
|
||||||
- Send Port nur bei `Status=Started`
|
- Send Port nur bei `Status=Started`
|
||||||
- dynamischer Send Port nie als statisches Ziel
|
- dynamischer Send Port nie als statisches Ziel
|
||||||
|
- Scheduling-Komponente nie als Netzwerkziel
|
||||||
|
|
||||||
Damit wird ein inzwischen gestopptes/deaktiviertes Artefakt sofort nicht mehr
|
Damit wird ein inzwischen gestopptes/deaktiviertes Artefakt sofort nicht mehr
|
||||||
geprüft. Prüffähige aktuelle WMI-Adressen werden bei jedem Minutenlauf direkt
|
geprüft. Prüffähige aktuelle WMI-Adressen werden bei jedem Minutenlauf direkt
|
||||||
@@ -58,7 +72,7 @@ werden nur:
|
|||||||
- Adaptername
|
- Adaptername
|
||||||
- `TCP` oder `UDP`
|
- `TCP` oder `UDP`
|
||||||
- Host und Port
|
- Host und Port
|
||||||
- `enabled` und `autoDiscovered`
|
- `enabled`, `autoDiscovered` und die Best-Effort-Kennzeichnung
|
||||||
|
|
||||||
URI-Benutzerinformationen, Kennwörter, Pfade und Querystrings werden nicht
|
URI-Benutzerinformationen, Kennwörter, Pfade und Querystrings werden nicht
|
||||||
geschrieben. Der Katalog liegt im bereits ACL-geschützten `data`-Verzeichnis;
|
geschrieben. Der Katalog liegt im bereits ACL-geschützten `data`-Verzeichnis;
|
||||||
@@ -122,6 +136,18 @@ passende nebenwirkungsfreie Netzwerktest. Er bestätigt DNS, Routing,
|
|||||||
Firewall und einen annehmenden TCP-Port, aber nicht die fachliche Antwort der
|
Firewall und einen annehmenden TCP-Port, aber nicht die fachliche Antwort der
|
||||||
Zielanwendung.
|
Zielanwendung.
|
||||||
|
|
||||||
|
Oracle-`DATABASE`-Adressen mit explizitem Anbietersegment `/Oracle` verwenden
|
||||||
|
den angegebenen Port oder den Oracle-Standard TCP 1521. WCF-SAP-Adressen
|
||||||
|
verwenden vorrangig den ersten `SAPROUTER=/H/host[/S/port]`-Hop, andernfalls
|
||||||
|
`ListenerGwHost`/`ListenerGwServ` beziehungsweise `GwHost`/`GwServ`.
|
||||||
|
`sapgwNN` wird als TCP `33NN` abgeleitet; der SAProuter-Standard ist TCP 3299.
|
||||||
|
Diese automatisch erkannten Adapterziele sind `bestEffort="true"`: Die Probe
|
||||||
|
wird ausgeführt und gemessen, ein Fehlschlag aber nur in
|
||||||
|
`biztalk_endpoints_best_effort_ignored` gezählt. Nicht sicher ableitbare
|
||||||
|
DATABASE-/WCF-SAP-Adressen werden erwartbar ausgenommen. Ein manueller
|
||||||
|
Katalogeintrag bleibt standardmäßig streng alarmierend und ist die bewusste
|
||||||
|
Option für verbindliche Überwachung.
|
||||||
|
|
||||||
UDP ist verbindungslos. Bei `udp://host:port` bestätigt der Check nur DNS,
|
UDP ist verbindungslos. Bei `udp://host:port` bestätigt der Check nur DNS,
|
||||||
Route und erfolgreichen lokalen Datagrammversand. Für einen belastbaren
|
Route und erfolgreichen lokalen Datagrammversand. Für einen belastbaren
|
||||||
UDP-Dienstcheck wäre ein adapter-/protokollspezifisches Request/Response-
|
UDP-Dienstcheck wäre ein adapter-/protokollspezifisches Request/Response-
|
||||||
|
|||||||
@@ -61,15 +61,18 @@ Artefakte. Nur die Textliste ist begrenzt.
|
|||||||
Gesund, ohne Auflistung aller Ziele:
|
Gesund, ohne Auflistung aller Ziele:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
0 "BizTalk ACC Endpoint Reachability" biztalk_endpoints_configured=63;;;0|biztalk_endpoints_active=61;;;0|biztalk_endpoints_unique_targets=44;;;0|biztalk_endpoints_tested=61;;;0|biztalk_endpoints_available=61;;;0|biztalk_endpoints_failed=0;;1;0|biztalk_endpoints_unresolved=0;;1;0|biztalk_endpoints_expected_non_socket=12;;;0|biztalk_endpoints_manual_overrides=0;;;0|biztalk_endpoints_inactive_skipped=2;;;0|biztalk_endpoint_probe_duration_ms=842;;;0 Alle 61 aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar (unique_targets=44, expected_non_socket=12, manual_overrides=0, probe_ms=842); catalog_utc=2026-08-04T10:00:00.0000000Z
|
0 "BizTalk ACC Endpoint Reachability" biztalk_endpoints_configured=63;;;0|biztalk_endpoints_active=61;;;0|biztalk_endpoints_unique_targets=44;;;0|biztalk_endpoints_tested=61;;;0|biztalk_endpoints_available=60;;;0|biztalk_endpoints_failed=0;;1;0|biztalk_endpoints_unresolved=0;;1;0|biztalk_endpoints_expected_non_socket=12;;;0|biztalk_endpoints_best_effort=9;;;0|biztalk_endpoints_best_effort_ignored=1;;;0|biztalk_endpoints_manual_overrides=0;;;0|biztalk_endpoints_inactive_skipped=2;;;0|biztalk_endpoint_probe_duration_ms=842;;;0 Alle regulaer alarmierenden aktiven Send-/Receive-Endpunkte sind erreichbar (active=61, available=60, unique_targets=44, expected_non_socket=12, best_effort=9, best_effort_ignored=1, manual_overrides=0, probe_ms=842); catalog_utc=2026-08-04T10:00:00.0000000Z
|
||||||
```
|
```
|
||||||
|
|
||||||
Fehlerhaft, nur mit nicht erreichbaren Zielen:
|
Fehlerhaft, nur mit nicht erreichbaren Zielen:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2 "BizTalk ACC Endpoint Reachability" ... Endpoint reachability active=61, tested=61, available=59, failed=2, unresolved=0, expected_non_socket=12, manual_overrides=0; unavailable=SendPort:Orders\SP_Order_API[Primary]->orders.example:443/TCP(TCP timeout), ReceiveLocation:Invoices\RL_SFTP[Inbound]->sftp.example:22/TCP(SocketError=ConnectionRefused)
|
2 "BizTalk ACC Endpoint Reachability" ... Endpoint reachability active=61, tested=61, available=58, failed=2, unresolved=0, expected_non_socket=12, best_effort=9, best_effort_ignored=1, manual_overrides=0; unavailable=SendPort:Orders\SP_Order_API[Primary]->orders.example:443/TCP(TCP timeout), ReceiveLocation:Invoices\RL_SFTP[Inbound]->sftp.example:22/TCP(SocketError=ConnectionRefused)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Der nicht verfügbare Best-Effort-Endpunkt wird nur gezählt und nicht in
|
||||||
|
`unavailable=` ausgegeben. Die beiden regulären Fehler bleiben CRIT.
|
||||||
|
|
||||||
## Fehlender oder veralteter Provider-Snapshot
|
## Fehlender oder veralteter Provider-Snapshot
|
||||||
|
|
||||||
Alle neun Services werden `UNKNOWN`, zum Beispiel:
|
Alle neun Services werden `UNKNOWN`, zum Beispiel:
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Analyse: Runtime-Abnahme mit Endpoint-Reachability `UNKNOWN`
|
||||||
|
|
||||||
|
## Beobachtung
|
||||||
|
|
||||||
|
Setup 2.2.5 schaltete die neue Version um, startete den Provider erfolgreich
|
||||||
|
und validierte danach die Runtime-Artefakte. Die Abnahme endete trotzdem mit:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Runtime validation failed: Snapshot enthaelt unzuverlaessige UNKNOWN-Zustaende
|
||||||
|
in stabilen Services. UNKNOWN=[BizTalk Endpoint Reachability].
|
||||||
|
```
|
||||||
|
|
||||||
|
Anschließend stellte der Installer die vorherige Version samt Task und frischem
|
||||||
|
Snapshot erfolgreich wieder her.
|
||||||
|
|
||||||
|
## Einordnung
|
||||||
|
|
||||||
|
`LastTaskResult=0` belegt, dass der Providerlauf technisch abgeschlossen und
|
||||||
|
ein Snapshot geschrieben wurde. Zusätzlich waren Snapshot-Zeitpunkt,
|
||||||
|
SHA-256, Maschine, Collector-Identität, alle neun Servicenamen und der im
|
||||||
|
Abnahmelauf synchronisierte Endpoint-Katalog valide. Das verbleibende
|
||||||
|
`UNKNOWN` war daher kein Installations- oder Transaktionsfehler, sondern der
|
||||||
|
fachliche Zustand der Endpoint-Prüfung.
|
||||||
|
|
||||||
|
Der Endpoint-Service verwendet `UNKNOWN`, wenn die Prüfabdeckung nicht
|
||||||
|
vollständig verlässlich ist, zum Beispiel bei einer nicht sicher auf
|
||||||
|
Host/Port reduzierbaren aktiven Adresse, einem Katalog-/Probehinweis oder
|
||||||
|
fehlendem manuellem Override. Diesen Zustand auf `OK` zu ändern würde die
|
||||||
|
Monitoringlücke verbergen.
|
||||||
|
|
||||||
|
## Korrektur in Version 2.2.6
|
||||||
|
|
||||||
|
- Snapshot, Erzeugungszeit, Integrität, Maschine und Collector-Identität
|
||||||
|
werden unverändert strikt geprüft.
|
||||||
|
- Alle neun stabilen Services müssen eindeutig vorhanden sein.
|
||||||
|
- Bei aktivierter Endpoint-Prüfung muss der Katalog weiterhin im gestarteten
|
||||||
|
Abnahmelauf erfolgreich synchronisiert worden sein.
|
||||||
|
- `UNKNOWN` in einem Kernservice bleibt ein Abnahmefehler.
|
||||||
|
- Ausschließlich `BizTalk Endpoint Reachability = UNKNOWN` wird als
|
||||||
|
nicht blockierende Betriebswarnung akzeptiert.
|
||||||
|
- Die vollständige UNKNOWN-Zeile erscheint in Setup-Ausgabe und Setup-Log und
|
||||||
|
bleibt unverändert im Snapshot für Checkmk erhalten.
|
||||||
|
|
||||||
|
Damit wird die Installation abgeschlossen, ohne einen ungeklärten
|
||||||
|
Monitoringzustand grünzufärben. Der normale Minutentask läuft weiter und die
|
||||||
|
Endpoint-Ursache kann anhand von `unresolved_endpoints`,
|
||||||
|
`catalog_or_probe_error`, `biztalk_endpoints_unresolved`, Katalog und
|
||||||
|
Provider-Log gezielt bearbeitet werden.
|
||||||
|
|
||||||
|
## Weiteres Vorgehen in ACC
|
||||||
|
|
||||||
|
1. Setup 2.2.6 als Update ausführen und die ausgegebene
|
||||||
|
`RUNTIME_VALIDATION_WARNING` sichern.
|
||||||
|
2. Im Snapshot die vollständige Zeile `BizTalk Endpoint Reachability` prüfen.
|
||||||
|
3. Bei `unresolved_endpoints` Adapter, Artefakt und Grund auswerten und nur
|
||||||
|
bei einem tatsächlich externen Ziel einen geheimnisfreien manuellen
|
||||||
|
Override in `endpoints.xml` ergänzen.
|
||||||
|
4. Bei `catalog_or_probe_error` zuerst Provider-Log und Katalog-XML prüfen.
|
||||||
|
5. Nach der Bereinigung zwei Minutenläufe und den Checkmk-Agent-Dump prüfen.
|
||||||
|
|
||||||
|
## Regressionstests
|
||||||
|
|
||||||
|
- Ein isoliertes Endpoint-Reachability-`UNKNOWN` wird als Warnung akzeptiert
|
||||||
|
und sein vollständiger Detailtext bleibt erhalten.
|
||||||
|
- `BizTalk Platform = UNKNOWN` bleibt blockierend.
|
||||||
|
- Fehlender Service, alter Snapshot, falsche Identität und alter Katalog
|
||||||
|
bleiben blockierend.
|
||||||
@@ -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.
|
||||||
@@ -53,6 +53,29 @@ Verbindlicher Agent-Dump:
|
|||||||
|
|
||||||
## Service Discovery
|
## Service Discovery
|
||||||
|
|
||||||
|
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
|
||||||
|
Endpoint-Prüfung – einen frisch synchronisierten Katalog erzeugt hat. Die neun
|
||||||
|
stabilen Services müssen vollständig vorliegen. `UNKNOWN` in einem Kernservice
|
||||||
|
bleibt blockierend. Ab Version 2.2.6 wird ein isoliertes
|
||||||
|
`BizTalk Endpoint Reachability = UNKNOWN` als fachliche Betriebswarnung
|
||||||
|
akzeptiert, sofern Snapshot, Identität und frisch synchronisierter Katalog
|
||||||
|
valide sind. Die UNKNOWN-Zeile bleibt in Checkmk sichtbar. 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.
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -109,6 +132,9 @@ Soll:
|
|||||||
- Receive Locations und Send Ports zeigen getrennte expected/unexpected Werte.
|
- Receive Locations und Send Ports zeigen getrennte expected/unexpected Werte.
|
||||||
- Endpoint Reachability zeigt bei Erfolg nur die Gesamtzahl und bei Fehlern
|
- Endpoint Reachability zeigt bei Erfolg nur die Gesamtzahl und bei Fehlern
|
||||||
ausschließlich nicht erreichbare oder wirklich unaufgelöste Ziele.
|
ausschließlich nicht erreichbare oder wirklich unaufgelöste Ziele.
|
||||||
|
- Schedule ist vollständig ausgenommen; Oracle-DATABASE und WCF-SAP laufen
|
||||||
|
als TCP-Best-Effort und können keinen Alarm auslösen. Die zugehörigen
|
||||||
|
Erfolgs-/Ignoriert-Zähler bleiben für die Betriebsanalyse sichtbar.
|
||||||
- keine berechtigungsbedingten `UNKNOWN`-Services.
|
- keine berechtigungsbedingten `UNKNOWN`-Services.
|
||||||
|
|
||||||
## Alarmierung der Transportkette
|
## Alarmierung der Transportkette
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# RTM-Adapterregeln für Endpoint Reachability
|
||||||
|
|
||||||
|
## Anlass
|
||||||
|
|
||||||
|
Die ACC-Ausgabe vom 11.08.2026 zeigte `BizTalk Endpoint Reachability =
|
||||||
|
UNKNOWN`, obwohl die eigentlichen Socket-Probes in wenigen Millisekunden
|
||||||
|
abgeschlossen waren. Die Detailausgabe bestand überwiegend aus drei
|
||||||
|
Adapterklassen, die der generische URI-Parser nicht fachgerecht einordnen
|
||||||
|
konnte:
|
||||||
|
|
||||||
|
- Scheduling-Komponenten ohne externes Netzwerkziel,
|
||||||
|
- kundenspezifische `DATABASE`-Adressen, nahezu ausschließlich Oracle,
|
||||||
|
- WCF-SAP-Verbindungs-URIs mit Gateway- und SAProuter-Parametern.
|
||||||
|
|
||||||
|
Diese Fälle dürfen den RTM-Service weder mit Auflösungslücken noch mit
|
||||||
|
Fehlalarmen belasten.
|
||||||
|
|
||||||
|
## Verbindliche Klassifizierung ab Version 2.2.7
|
||||||
|
|
||||||
|
### Scheduling
|
||||||
|
|
||||||
|
Adaptername oder Adresse mit `Schedule`/`Scheduler` wird vollständig als
|
||||||
|
erwartbar ausgenommen. Es erfolgt keine Netzwerkprobe und kein UNKNOWN.
|
||||||
|
|
||||||
|
### DATABASE / Oracle
|
||||||
|
|
||||||
|
Nur die eindeutig erkennbare Form
|
||||||
|
|
||||||
|
```text
|
||||||
|
Database://host[:port]/.../Oracle
|
||||||
|
```
|
||||||
|
|
||||||
|
wird automatisch aufgelöst. Ein expliziter Port gewinnt; andernfalls wird
|
||||||
|
TCP 1521 verwendet. Andere DATABASE-Anbieter oder nicht eindeutig lesbare
|
||||||
|
Adressen werden erwartbar ausgenommen. Es wird kein UDP-Test erzeugt.
|
||||||
|
|
||||||
|
Oracle dokumentiert TCP 1521 als Standardadresse des Listeners und nimmt bei
|
||||||
|
fehlender Portangabe im Easy-Connect-Format ebenfalls 1521 an:
|
||||||
|
|
||||||
|
- [Oracle: Configuring the Network Environment](https://docs.oracle.com/en/database/oracle/oracle-database/19/admqs/configuring-the-network-environment.html)
|
||||||
|
- [Oracle: Configuring and Administering Oracle Net Listener](https://docs.oracle.com/en/database/oracle/oracle-database/21/netag/configuring-and-administering-oracle-net-listener.html)
|
||||||
|
|
||||||
|
### WCF-SAP
|
||||||
|
|
||||||
|
Microsoft dokumentiert `ListenerGwHost`, `ListenerGwServ` und `SAPROUTER` als
|
||||||
|
Bestandteile der SAP-Verbindungs-URI. Der Parser verwendet in dieser
|
||||||
|
Reihenfolge:
|
||||||
|
|
||||||
|
1. erster SAProuter-Hop aus `/H/host/S/port`,
|
||||||
|
2. `ListenerGwHost` und `ListenerGwServ`,
|
||||||
|
3. `GwHost` und `GwServ` als Fallback.
|
||||||
|
|
||||||
|
Numerische Dienste werden direkt übernommen. `sapgwNN` wird auf TCP `33NN`
|
||||||
|
abgebildet. Fehlt bei einem SAProuter-Hop `/S/`, gilt der dokumentierte
|
||||||
|
Standard TCP 3299. URI-Escapes werden vor der Auswertung dekodiert; Kennwörter,
|
||||||
|
Program-ID, Pfade und Querystrings gelangen weiterhin nicht in Katalog, Log
|
||||||
|
oder Checkmk-Ausgabe.
|
||||||
|
|
||||||
|
Quellen:
|
||||||
|
|
||||||
|
- [Microsoft Learn: Create the SAP system connection URI](https://learn.microsoft.com/en-us/biztalk/adapters-and-accelerators/adapter-sap/create-the-sap-system-connection-uri)
|
||||||
|
- [SAP Help: Entries in the Services File](https://help.sap.com/docs/SLTOOLSET/6bc963751d0840f996c5c9d7a7f83473/bb9faaebe0c74a58bbcc259c83f59437.html)
|
||||||
|
- [SAP Help: SAProuter Host and Port](https://help.sap.com/docs/SAP_NETWEAVER_AS_ABAP_FOR_SOH_740/e245703406684d8a81812f4c6334eb2f/486b5b0cb74c07bee10000000a42189d.html)
|
||||||
|
|
||||||
|
## Best-Effort statt Alarm
|
||||||
|
|
||||||
|
Automatisch abgeleitete DATABASE-/WCF-SAP-Ziele erhalten im lokalen Katalog
|
||||||
|
`bestEffort="true"`. Sie werden normal dedupliziert und per TCP verbunden.
|
||||||
|
|
||||||
|
- Erfolg erhöht die getesteten und verfügbaren Zähler.
|
||||||
|
- Timeout, DNS- oder Verbindungsfehler erhöht
|
||||||
|
`biztalk_endpoints_best_effort_ignored`.
|
||||||
|
- Der Fehler erscheint nicht als `unavailable=`, erzeugt keinen Warn-Logeintrag
|
||||||
|
pro Minute und ändert den Checkmk-Status nicht.
|
||||||
|
- Eine nicht ableitbare Adapteradresse zählt zu
|
||||||
|
`biztalk_endpoints_expected_non_socket`, nicht zu `unresolved`.
|
||||||
|
|
||||||
|
Damit erfüllt der Check die gewünschte „versuchen, sonst ignorieren“-Semantik.
|
||||||
|
Soll ein solches Ziel später verbindlich alarmieren, wird ein manueller
|
||||||
|
Katalogeintrag mit `autoDiscovered="false"` und ohne
|
||||||
|
`bestEffort="true"` hinterlegt. Reguläre automatisch erkannte Ziele bleiben
|
||||||
|
ebenfalls streng: Ihre Nichterreichbarkeit ist weiterhin CRIT.
|
||||||
|
|
||||||
|
## Regression und Abnahme
|
||||||
|
|
||||||
|
Die automatisierten Tests decken Scheduler-Ausschluss, Oracle-Standard- und
|
||||||
|
Explizitport, nicht unterstützte DATABASE-Anbieter, WCF-SAP-Gateway,
|
||||||
|
URI-kodierten SAProuter, zielbasierte SAP-Konfiguration ohne ableitbaren Host,
|
||||||
|
Geheimnisschutz und die nicht alarmierende Best-Effort-Ausgabe ab.
|
||||||
|
|
||||||
|
Für die Windows-/ACC-Abnahme nach Installation von 2.2.7:
|
||||||
|
|
||||||
|
1. Im Provider-Log muss der erzwungene Katalogabgleich erfolgreich sein.
|
||||||
|
2. `BizTalk Endpoint Reachability` darf wegen Schedule, DATABASE oder WCF-SAP
|
||||||
|
nicht mehr UNKNOWN werden.
|
||||||
|
3. `biztalk_endpoints_unresolved` darf diese drei Klassen nicht enthalten.
|
||||||
|
4. `biztalk_endpoints_best_effort` zeigt die tatsächlich abgeleiteten Oracle-
|
||||||
|
und SAP-Ziele; nicht erreichbare davon erscheinen nur in
|
||||||
|
`biztalk_endpoints_best_effort_ignored`.
|
||||||
|
5. Ein absichtlich nicht erreichbares reguläres Testziel muss weiterhin CRIT
|
||||||
|
auslösen, damit die strenge Standardalarmierung belegt bleibt.
|
||||||
@@ -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.6 | 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.3"
|
core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.6"
|
||||||
|
|
||||||
# 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.3"),
|
("Lösungsstand", "BizTalk Checkmk Pulse 2.2.6"),
|
||||||
("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,9 @@ 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. Kernservice-UNKNOWN bleibt blockierend; ein isoliertes Endpoint-Reachability-UNKNOWN bleibt als sichtbare Betriebswarnung erhalten und löst keinen Rollback aus.")
|
||||||
|
|
||||||
add_heading(document, "Sicherheitsmodell", level=2)
|
add_heading(document, "Sicherheitsmodell", level=2)
|
||||||
add_table(
|
add_table(
|
||||||
@@ -572,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.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.6 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,10 +3,12 @@ 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;
|
||||||
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 +16,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;
|
||||||
@@ -35,40 +38,62 @@ 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 taskRemoved = false;
|
var hadExistingExecutable = false;
|
||||||
|
var mutationStarted = false;
|
||||||
var backupCreated = false;
|
var backupCreated = false;
|
||||||
var filesActivated = false;
|
var filesActivated = false;
|
||||||
var scheduler = new TaskSchedulerService();
|
var scheduler = new TaskSchedulerService();
|
||||||
|
|
||||||
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";
|
||||||
@@ -78,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;
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
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);
|
||||||
@@ -122,35 +154,75 @@ 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.");
|
||||||
|
|
||||||
scheduler.RegisterAndStart(
|
phase = "Provider-Abnahmelauf registrieren und starten";
|
||||||
|
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);
|
|
||||||
|
phase = "Provider-Abnahmelauf abwarten";
|
||||||
|
var completedRun = scheduler.WaitForSuccessfulRun(
|
||||||
|
TaskName,
|
||||||
|
runtimeValidationStartedUtc,
|
||||||
|
PostInstallValidationTimeout,
|
||||||
|
report);
|
||||||
|
report("Provider-Abnahmelauf erfolgreich: LastTaskResult=" + completedRun.ExitCode + ".");
|
||||||
|
|
||||||
|
phase = "Runtime-Artefakte validieren";
|
||||||
|
var runtimeValidation = RunRuntimeValidation(
|
||||||
|
targetExe,
|
||||||
|
runtimeValidationStartedUtc,
|
||||||
|
account);
|
||||||
|
report("Post-Install-Runtime-Abnahme erfolgreich: " + runtimeValidation);
|
||||||
|
|
||||||
|
phase = "Minuetlichen Scheduled Task registrieren";
|
||||||
|
scheduler.RegisterRecurring(
|
||||||
|
TaskName,
|
||||||
|
targetExe,
|
||||||
|
installDirectory,
|
||||||
|
account,
|
||||||
|
isGmsa ? null : password,
|
||||||
|
isGmsa);
|
||||||
|
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)
|
||||||
|
{
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filesActivated || backupCreated)
|
if (filesActivated || backupCreated)
|
||||||
@@ -164,47 +236,68 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mutationStarted)
|
||||||
|
{
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,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,
|
||||||
@@ -427,22 +532,119 @@ 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(
|
||||||
|
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 outputRead = process.StandardOutput.ReadToEndAsync();
|
||||||
|
var errorRead = process.StandardError.ReadToEndAsync();
|
||||||
|
if (!process.WaitForExit(30000))
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
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
|
||||||
|
|| !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)
|
||||||
@@ -466,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. "
|
||||||
@@ -477,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.3.0")]
|
[assembly: AssemblyVersion("2.2.7.0")]
|
||||||
[assembly: AssemblyFileVersion("2.2.3.0")]
|
[assembly: AssemblyFileVersion("2.2.7.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,20 +13,67 @@ 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 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 +101,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 +117,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 +132,154 @@ 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 (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)
|
||||||
|
{
|
||||||
|
report(
|
||||||
|
"Warte auf erfolgreichen Providerlauf: state=" + DescribeState(lastState)
|
||||||
|
+ ", last_result=" + DescribeResult(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=" + DescribeState(lastState)
|
||||||
|
+ ", last_result=" + DescribeResult(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 == TaskStateReady
|
||||||
|
&& lastTaskResult == 0
|
||||||
|
&& lastRunUtc.HasValue
|
||||||
|
// Task Scheduler kann LastRunTime mit geringerer Aufloesung als DateTime.UtcNow liefern.
|
||||||
|
&& 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;
|
||||||
@@ -131,4 +332,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" />
|
||||||
|
|||||||
@@ -357,6 +357,8 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Formatiert die Netzwerk-Erreichbarkeit aller aktuell aktiven, pruefbaren BizTalk-Endpunkte.
|
/// Formatiert die Netzwerk-Erreichbarkeit aller aktuell aktiven, pruefbaren BizTalk-Endpunkte.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="result">Vollstaendiges Provider-Ergebnis mit Endpoint-Zustand.</param>
|
||||||
|
/// <returns>Eine syntaktisch gueltige Checkmk-Local-Check-Zeile.</returns>
|
||||||
private string FormatEndpointConnectivity(ProbeResult result)
|
private string FormatEndpointConnectivity(ProbeResult result)
|
||||||
{
|
{
|
||||||
var endpointState = result.EndpointConnectivity;
|
var endpointState = result.EndpointConnectivity;
|
||||||
@@ -378,7 +380,12 @@ namespace BizTalkCheckmkPulse
|
|||||||
"Endpoint-Pruefung nicht verlaesslich moeglich. " + EmptyAsUnknown(endpointState.Failure));
|
"Endpoint-Pruefung nicht verlaesslich moeglich. " + EmptyAsUnknown(endpointState.Failure));
|
||||||
}
|
}
|
||||||
|
|
||||||
var failed = endpointState.Results.Where(x => !x.Available).ToArray();
|
var failed = endpointState.Results
|
||||||
|
.Where(x => !x.Available && !x.Endpoint.BestEffort)
|
||||||
|
.ToArray();
|
||||||
|
var ignoredBestEffort = endpointState.Results
|
||||||
|
.Where(x => !x.Available && x.Endpoint.BestEffort)
|
||||||
|
.ToArray();
|
||||||
var available = endpointState.Results.Count(x => x.Available);
|
var available = endpointState.Results.Count(x => x.Available);
|
||||||
var incomplete = endpointState.Results.Count != endpointState.Active
|
var incomplete = endpointState.Results.Count != endpointState.Active
|
||||||
|| endpointState.UnresolvedActive > 0
|
|| endpointState.UnresolvedActive > 0
|
||||||
@@ -391,7 +398,7 @@ namespace BizTalkCheckmkPulse
|
|||||||
: CheckState.Ok;
|
: CheckState.Ok;
|
||||||
var metrics = string.Format(
|
var metrics = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_unique_targets={2};;;0|biztalk_endpoints_tested={3};;;0|biztalk_endpoints_available={4};;;0|biztalk_endpoints_failed={5};;1;0|biztalk_endpoints_unresolved={6};;1;0|biztalk_endpoints_expected_non_socket={7};;;0|biztalk_endpoints_manual_overrides={8};;;0|biztalk_endpoints_inactive_skipped={9};;;0|biztalk_endpoint_probe_duration_ms={10};;;0",
|
"biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_unique_targets={2};;;0|biztalk_endpoints_tested={3};;;0|biztalk_endpoints_available={4};;;0|biztalk_endpoints_failed={5};;1;0|biztalk_endpoints_unresolved={6};;1;0|biztalk_endpoints_expected_non_socket={7};;;0|biztalk_endpoints_best_effort={8};;;0|biztalk_endpoints_best_effort_ignored={9};;;0|biztalk_endpoints_manual_overrides={10};;;0|biztalk_endpoints_inactive_skipped={11};;;0|biztalk_endpoint_probe_duration_ms={12};;;0",
|
||||||
endpointState.Configured,
|
endpointState.Configured,
|
||||||
endpointState.Active,
|
endpointState.Active,
|
||||||
endpointState.UniqueTargets,
|
endpointState.UniqueTargets,
|
||||||
@@ -400,15 +407,21 @@ namespace BizTalkCheckmkPulse
|
|||||||
failed.Length,
|
failed.Length,
|
||||||
endpointState.UnresolvedActive,
|
endpointState.UnresolvedActive,
|
||||||
endpointState.ExpectedNonProbeableActive,
|
endpointState.ExpectedNonProbeableActive,
|
||||||
|
endpointState.BestEffortActive,
|
||||||
|
ignoredBestEffort.Length,
|
||||||
endpointState.ManualOverridesActive,
|
endpointState.ManualOverridesActive,
|
||||||
endpointState.SkippedInactive,
|
endpointState.SkippedInactive,
|
||||||
endpointState.ProbeDurationMilliseconds);
|
endpointState.ProbeDurationMilliseconds);
|
||||||
var detail = new StringBuilder();
|
var detail = new StringBuilder();
|
||||||
if (failed.Length == 0 && !incomplete)
|
if (failed.Length == 0 && !incomplete)
|
||||||
{
|
{
|
||||||
detail.Append("Alle ").Append(endpointState.Active).Append(" aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar")
|
detail.Append("Alle regulaer alarmierenden aktiven Send-/Receive-Endpunkte sind erreichbar")
|
||||||
.Append(" (unique_targets=").Append(endpointState.UniqueTargets)
|
.Append(" (active=").Append(endpointState.Active)
|
||||||
|
.Append(", available=").Append(available)
|
||||||
|
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
|
||||||
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
|
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
|
||||||
|
.Append(", best_effort=").Append(endpointState.BestEffortActive)
|
||||||
|
.Append(", best_effort_ignored=").Append(ignoredBestEffort.Length)
|
||||||
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
|
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
|
||||||
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds).Append(")");
|
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds).Append(")");
|
||||||
}
|
}
|
||||||
@@ -420,6 +433,8 @@ namespace BizTalkCheckmkPulse
|
|||||||
.Append(", failed=").Append(failed.Length)
|
.Append(", failed=").Append(failed.Length)
|
||||||
.Append(", unresolved=").Append(endpointState.UnresolvedActive)
|
.Append(", unresolved=").Append(endpointState.UnresolvedActive)
|
||||||
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
|
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
|
||||||
|
.Append(", best_effort=").Append(endpointState.BestEffortActive)
|
||||||
|
.Append(", best_effort_ignored=").Append(ignoredBestEffort.Length)
|
||||||
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
|
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
|
||||||
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
|
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
|
||||||
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds);
|
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds);
|
||||||
|
|||||||
@@ -13,9 +13,17 @@ namespace BizTalkCheckmkPulse
|
|||||||
@"^(?<host>\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):(?<port>\d{1,5})(?:[/\\].*)?$",
|
@"^(?<host>\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):(?<port>\d{1,5})(?:[/\\].*)?$",
|
||||||
RegexOptions.CultureInvariant);
|
RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
|
private static readonly Regex DatabaseProviderPattern = new Regex(
|
||||||
|
@"/(?:Oracle|SQLServer)(?:[/;?]|$)",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag.
|
/// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="candidate">Aktiver BizTalk-Transport mit Adaptername und Adresse.</param>
|
||||||
|
/// <param name="entry">Geheimnisfreier Katalogeintrag, sofern ein Ziel bestimmt wurde.</param>
|
||||||
|
/// <param name="reason">Begruendung, wenn kein pruefbares Ziel bestimmt wurde.</param>
|
||||||
|
/// <returns><see langword="true"/>, wenn Host, Port und Protokoll sicher bestimmt wurden.</returns>
|
||||||
public static bool TryCreate(
|
public static bool TryCreate(
|
||||||
EndpointCandidate candidate,
|
EndpointCandidate candidate,
|
||||||
out EndpointCatalogEntry entry,
|
out EndpointCatalogEntry entry,
|
||||||
@@ -30,6 +38,8 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trennt sicher pruefbare Ziele von erwartbar lokalen/adapterinternen Adressen und echten Aufloesungsluecken.
|
/// Trennt sicher pruefbare Ziele von erwartbar lokalen/adapterinternen Adressen und echten Aufloesungsluecken.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="candidate">Zu klassifizierender BizTalk-Transport.</param>
|
||||||
|
/// <returns>Klassifizierung sowie gegebenenfalls das pruefbare Socket-Ziel.</returns>
|
||||||
public static EndpointResolution Analyze(EndpointCandidate candidate)
|
public static EndpointResolution Analyze(EndpointCandidate candidate)
|
||||||
{
|
{
|
||||||
if (candidate == null)
|
if (candidate == null)
|
||||||
@@ -42,6 +52,13 @@ namespace BizTalkCheckmkPulse
|
|||||||
return Expected("Dynamischer Send Port besitzt kein statisch pruefbares Ziel.");
|
return Expected("Dynamischer Send Port besitzt kein statisch pruefbares Ziel.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scheduling-Adapter bilden zeitliche BizTalk-Steuerung ab und besitzen kein
|
||||||
|
// fachlich relevantes Remoteziel. Sie werden bewusst vollstaendig ausgeklammert.
|
||||||
|
if (IsScheduler(candidate))
|
||||||
|
{
|
||||||
|
return Expected("Scheduling-Komponente ist von der Endpoint-Pruefung ausgenommen.");
|
||||||
|
}
|
||||||
|
|
||||||
var address = (candidate.Address ?? string.Empty).Trim();
|
var address = (candidate.Address ?? string.Empty).Trim();
|
||||||
if (address.Length == 0)
|
if (address.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -80,6 +97,33 @@ namespace BizTalkCheckmkPulse
|
|||||||
return Expected("Relative Receive-Listener-Adresse enthaelt keine eindeutige Site-Bindung.");
|
return Expected("Relative Receive-Listener-Adresse enthaelt keine eindeutige Site-Bindung.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DATABASE und WCF-SAP liefern Adapteradressen, deren Socket-Ziel nicht von
|
||||||
|
// System.Uri abgeleitet werden kann. Eine fehlende sichere Ableitung ist bei
|
||||||
|
// diesen Best-Effort-Adaptern kein Abdeckungsfehler.
|
||||||
|
if (IsDatabaseAdapter(candidate))
|
||||||
|
{
|
||||||
|
EndpointCatalogEntry databaseEntry;
|
||||||
|
string databaseReason;
|
||||||
|
if (TryResolveDatabase(candidate, address, out databaseEntry, out databaseReason))
|
||||||
|
{
|
||||||
|
return Probeable(databaseEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expected(databaseReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsWcfSapAdapter(candidate))
|
||||||
|
{
|
||||||
|
EndpointCatalogEntry sapEntry;
|
||||||
|
string sapReason;
|
||||||
|
if (TryResolveWcfSap(candidate, address, out sapEntry, out sapReason))
|
||||||
|
{
|
||||||
|
return Probeable(sapEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expected(sapReason);
|
||||||
|
}
|
||||||
|
|
||||||
EndpointCatalogEntry entry;
|
EndpointCatalogEntry entry;
|
||||||
string protocol;
|
string protocol;
|
||||||
string host;
|
string host;
|
||||||
@@ -115,8 +159,306 @@ namespace BizTalkCheckmkPulse
|
|||||||
Host = host,
|
Host = host,
|
||||||
Port = port,
|
Port = port,
|
||||||
Enabled = true,
|
Enabled = true,
|
||||||
AutoDiscovered = true
|
AutoDiscovered = true,
|
||||||
|
BestEffort = false
|
||||||
};
|
};
|
||||||
|
return Probeable(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidate">Zu klassifizierender BizTalk-Transport.</param>
|
||||||
|
/// <returns><see langword="true"/> nur bei einer echten Aufloesungsluecke.</returns>
|
||||||
|
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
|
||||||
|
{
|
||||||
|
return Analyze(candidate).Status == EndpointResolutionStatus.Unresolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Leitet das Oracle-TCP-Ziel aus der expliziten DATABASE-Adresse ab.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidate">BizTalk-Artefakt, aus dem der Katalogeintrag aufgebaut wird.</param>
|
||||||
|
/// <param name="address">Vollstaendige DATABASE-Transportadresse.</param>
|
||||||
|
/// <param name="entry">Abgeleiteter geheimnisfreier Best-Effort-Eintrag.</param>
|
||||||
|
/// <param name="reason">Begruendung, wenn Host oder Anbieter nicht sicher erkennbar ist.</param>
|
||||||
|
/// <returns><see langword="true"/>, wenn ein Oracle-Host und TCP-Port bestimmt wurden.</returns>
|
||||||
|
private static bool TryResolveDatabase(
|
||||||
|
EndpointCandidate candidate,
|
||||||
|
string address,
|
||||||
|
out EndpointCatalogEntry entry,
|
||||||
|
out string reason)
|
||||||
|
{
|
||||||
|
entry = null;
|
||||||
|
reason = "DATABASE-Adresse enthaelt keinen sicher bestimmbaren Oracle-Listener.";
|
||||||
|
if (!address.StartsWith("database://", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| !DatabaseProviderPattern.IsMatch(address))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri;
|
||||||
|
if (!Uri.TryCreate(address, UriKind.Absolute, out uri)
|
||||||
|
|| string.IsNullOrWhiteSpace(uri.Host))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oracle = Regex.IsMatch(
|
||||||
|
address,
|
||||||
|
@"/Oracle(?:[/;?]|$)",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
if (!oracle)
|
||||||
|
{
|
||||||
|
reason = "Nicht-Oracle-DATABASE-Ziel wird ohne explizite Betriebsfreigabe nicht automatisch geprueft.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var port = HasExplicitPort(address) ? uri.Port : 1521;
|
||||||
|
string validationReason;
|
||||||
|
var host = NormalizeHost(uri.Host);
|
||||||
|
if (!Validate(host, port, out validationReason))
|
||||||
|
{
|
||||||
|
reason = validationReason;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = CreateAdapterEntry(candidate, host, port);
|
||||||
|
reason = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Leitet den ersten erreichbaren TCP-Hop einer WCF-SAP-Verbindung ab.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidate">BizTalk-Artefakt, aus dem der Katalogeintrag aufgebaut wird.</param>
|
||||||
|
/// <param name="address">Vollstaendige SAP-Verbindungs-URI.</param>
|
||||||
|
/// <param name="entry">Abgeleiteter geheimnisfreier Best-Effort-Eintrag.</param>
|
||||||
|
/// <param name="reason">Begruendung, wenn Gateway oder SAProuter nicht sicher bestimmbar ist.</param>
|
||||||
|
/// <returns><see langword="true"/>, wenn ein SAProuter- oder Gateway-Ziel bestimmt wurde.</returns>
|
||||||
|
private static bool TryResolveWcfSap(
|
||||||
|
EndpointCandidate candidate,
|
||||||
|
string address,
|
||||||
|
out EndpointCatalogEntry entry,
|
||||||
|
out string reason)
|
||||||
|
{
|
||||||
|
entry = null;
|
||||||
|
reason = "WCF-SAP-Adresse enthaelt keinen sicher bestimmbaren Gateway- oder SAProuter-Port.";
|
||||||
|
var decoded = DecodeAdapterAddress(address);
|
||||||
|
|
||||||
|
string route;
|
||||||
|
if (TryReadParameter(decoded, "SAPROUTER", out route))
|
||||||
|
{
|
||||||
|
string routerHost;
|
||||||
|
int routerPort;
|
||||||
|
if (TryParseSapRouter(route, out routerHost, out routerPort))
|
||||||
|
{
|
||||||
|
entry = CreateAdapterEntry(candidate, routerHost, routerPort);
|
||||||
|
reason = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string gatewayHost;
|
||||||
|
string gatewayService;
|
||||||
|
if ((!TryReadParameter(decoded, "ListenerGwHost", out gatewayHost)
|
||||||
|
|| !TryReadParameter(decoded, "ListenerGwServ", out gatewayService))
|
||||||
|
&& (!TryReadParameter(decoded, "GwHost", out gatewayHost)
|
||||||
|
|| !TryReadParameter(decoded, "GwServ", out gatewayService)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int gatewayPort;
|
||||||
|
if (!TryResolveSapService(gatewayService, out gatewayPort))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gatewayHost = NormalizeHost(gatewayHost);
|
||||||
|
string validationReason;
|
||||||
|
if (!Validate(gatewayHost, gatewayPort, out validationReason))
|
||||||
|
{
|
||||||
|
reason = validationReason;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = CreateAdapterEntry(candidate, gatewayHost, gatewayPort);
|
||||||
|
reason = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baut einen TCP-Katalogeintrag fuer einen automatisch abgeleiteten Best-Effort-Adapter.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidate">Quellartefakt fuer Metadaten und stabilen Schluessel.</param>
|
||||||
|
/// <param name="host">Bereinigter DNS-Name oder IP-Adresse.</param>
|
||||||
|
/// <param name="port">Gueltiger TCP-Zielport.</param>
|
||||||
|
/// <returns>Automatisch verwalteter, nicht alarmierender Katalogeintrag.</returns>
|
||||||
|
private static EndpointCatalogEntry CreateAdapterEntry(EndpointCandidate candidate, string host, int port)
|
||||||
|
{
|
||||||
|
return new EndpointCatalogEntry
|
||||||
|
{
|
||||||
|
Key = candidate.Key,
|
||||||
|
ArtifactType = candidate.ArtifactType,
|
||||||
|
ApplicationName = candidate.ApplicationName,
|
||||||
|
ArtifactName = candidate.ArtifactName,
|
||||||
|
TransportRole = candidate.TransportRole,
|
||||||
|
AdapterName = candidate.AdapterName,
|
||||||
|
Protocol = "TCP",
|
||||||
|
Host = host,
|
||||||
|
Port = port,
|
||||||
|
Enabled = true,
|
||||||
|
AutoDiscovered = true,
|
||||||
|
BestEffort = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Liest einen URI-Parameter auch aus SAP-URIs mit wiederholtem Fragezeichen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Dekodierte SAP-Verbindungsadresse.</param>
|
||||||
|
/// <param name="name">Name des gesuchten Query-Parameters.</param>
|
||||||
|
/// <param name="value">Bereinigter Parameterwert.</param>
|
||||||
|
/// <returns><see langword="true"/>, wenn ein nicht leerer Wert gefunden wurde.</returns>
|
||||||
|
private static bool TryReadParameter(string address, string name, out string value)
|
||||||
|
{
|
||||||
|
value = string.Empty;
|
||||||
|
var match = Regex.Match(
|
||||||
|
address ?? string.Empty,
|
||||||
|
@"(?:^|[?&])" + Regex.Escape(name) + @"=(?<value>[^?&]+)",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = match.Groups["value"].Value.Trim();
|
||||||
|
return value.Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dekodiert URI-Escapes, ohne eine fehlerhafte Adapteradresse zum Laufzeitfehler zu machen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Rohe BizTalk-Transportadresse.</param>
|
||||||
|
/// <returns>Bestmoeglich dekodierte Adresse.</returns>
|
||||||
|
private static string DecodeAdapterAddress(string address)
|
||||||
|
{
|
||||||
|
var normalized = (address ?? string.Empty).Replace("&", "&");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Uri.UnescapeDataString(normalized);
|
||||||
|
}
|
||||||
|
catch (UriFormatException)
|
||||||
|
{
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bestimmt Host und Port des ersten SAProuter-Hops einer Route.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="route">SAProuter-Route im Format <c>/H/host/S/port</c>.</param>
|
||||||
|
/// <param name="host">Host des ersten Router-Hops.</param>
|
||||||
|
/// <param name="port">Expliziter Port oder SAProuter-Standardport 3299.</param>
|
||||||
|
/// <returns><see langword="true"/>, wenn der erste Hop gueltig ist.</returns>
|
||||||
|
private static bool TryParseSapRouter(string route, out string host, out int port)
|
||||||
|
{
|
||||||
|
host = string.Empty;
|
||||||
|
port = 0;
|
||||||
|
var match = Regex.Match(
|
||||||
|
route ?? string.Empty,
|
||||||
|
@"/H/(?<host>[^/]+)(?:/S/(?<service>[^/]+))?",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
host = NormalizeHost(match.Groups["host"].Value);
|
||||||
|
port = 3299;
|
||||||
|
var service = match.Groups["service"].Value;
|
||||||
|
if (service.Length > 0 && !TryResolveSapService(service, out port))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string reason;
|
||||||
|
return Validate(host, port, out reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uebersetzt numerische SAP-Dienste und sapgwNN in einen TCP-Port.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="service">Numerischer Port oder SAP-Gateway-Dienstname.</param>
|
||||||
|
/// <param name="port">Aufgeloester TCP-Port.</param>
|
||||||
|
/// <returns><see langword="true"/> bei einem gueltigen Port oder <c>sapgwNN</c>.</returns>
|
||||||
|
private static bool TryResolveSapService(string service, out int port)
|
||||||
|
{
|
||||||
|
port = 0;
|
||||||
|
var normalized = (service ?? string.Empty).Trim();
|
||||||
|
if (int.TryParse(normalized, out port))
|
||||||
|
{
|
||||||
|
return port >= 1 && port <= 65535;
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = Regex.Match(
|
||||||
|
normalized,
|
||||||
|
@"^sapgw(?<instance>\d{2})$",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||||
|
int instance;
|
||||||
|
if (!match.Success || !int.TryParse(match.Groups["instance"].Value, out instance))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
port = 3300 + instance;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Erkennt BizTalk-Scheduling-Komponenten, die kein Remoteziel darstellen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
|
||||||
|
/// <returns><see langword="true"/> fuer Schedule-/Scheduler-Konfigurationen.</returns>
|
||||||
|
private static bool IsScheduler(EndpointCandidate candidate)
|
||||||
|
{
|
||||||
|
return Contains(candidate.AdapterName, "SCHEDUL")
|
||||||
|
|| (candidate.Address ?? string.Empty).StartsWith("schedule", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Erkennt den kundenspezifischen DATABASE-Adapter und dessen URI-Schema.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
|
||||||
|
/// <returns><see langword="true"/> fuer DATABASE-Konfigurationen.</returns>
|
||||||
|
private static bool IsDatabaseAdapter(EndpointCandidate candidate)
|
||||||
|
{
|
||||||
|
return Contains(candidate.AdapterName, "DATABASE")
|
||||||
|
|| (candidate.Address ?? string.Empty).StartsWith("database://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Erkennt WCF-SAP-Adapter, ohne beliebige Adapter mit SAP im Namen einzubeziehen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
|
||||||
|
/// <returns><see langword="true"/> fuer eindeutig erkannte WCF-SAP-Konfigurationen.</returns>
|
||||||
|
private static bool IsWcfSapAdapter(EndpointCandidate candidate)
|
||||||
|
{
|
||||||
|
return Contains(candidate.AdapterName, "WCF-SAP")
|
||||||
|
|| Contains(candidate.AdapterName, "WCF SAP")
|
||||||
|
// BizTalk meldet den SAP Binding-Typ in manchen Umgebungen nur als
|
||||||
|
// WCF-Custom; das eindeutige sap://-Schema bleibt dennoch erhalten.
|
||||||
|
|| (candidate.Address ?? string.Empty).StartsWith("sap://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verpackt einen gueltigen Katalogeintrag als pruefbare Aufloesung.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry">Vollstaendig validierter Katalogeintrag.</param>
|
||||||
|
/// <returns>Pruefbare Endpoint-Aufloesung ohne Fehlergrund.</returns>
|
||||||
|
private static EndpointResolution Probeable(EndpointCatalogEntry entry)
|
||||||
|
{
|
||||||
return new EndpointResolution
|
return new EndpointResolution
|
||||||
{
|
{
|
||||||
Status = EndpointResolutionStatus.Probeable,
|
Status = EndpointResolutionStatus.Probeable,
|
||||||
@@ -125,14 +467,6 @@ namespace BizTalkCheckmkPulse
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
|
|
||||||
/// </summary>
|
|
||||||
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
|
|
||||||
{
|
|
||||||
return Analyze(candidate).Status == EndpointResolutionStatus.Unresolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryResolve(
|
private static bool TryResolve(
|
||||||
string address,
|
string address,
|
||||||
string adapterName,
|
string adapterName,
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ namespace BizTalkCheckmkPulse
|
|||||||
private readonly int _maxBytes;
|
private readonly int _maxBytes;
|
||||||
private readonly int _maxEntries;
|
private readonly int _maxEntries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialisiert den validierenden XML-Katalogspeicher.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Absoluter Zielpfad der Katalogdatei.</param>
|
||||||
|
/// <param name="maxBytes">Maximal akzeptierte Dateigroesse.</param>
|
||||||
|
/// <param name="maxEntries">Maximal akzeptierte Anzahl Eintraege.</param>
|
||||||
public EndpointCatalogStore(string path, int maxBytes, int maxEntries)
|
public EndpointCatalogStore(string path, int maxBytes, int maxEntries)
|
||||||
{
|
{
|
||||||
_path = path;
|
_path = path;
|
||||||
@@ -27,6 +33,11 @@ namespace BizTalkCheckmkPulse
|
|||||||
_maxEntries = maxEntries;
|
_maxEntries = maxEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Liest und validiert einen maschinen- und umgebungsgebundenen Katalog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="environmentName">Erwartete Monitoring-Umgebung.</param>
|
||||||
|
/// <returns>Vollstaendig validierter Endpoint-Katalog.</returns>
|
||||||
public EndpointCatalog Read(string environmentName)
|
public EndpointCatalog Read(string environmentName)
|
||||||
{
|
{
|
||||||
var info = new FileInfo(_path);
|
var info = new FileInfo(_path);
|
||||||
@@ -112,7 +123,9 @@ namespace BizTalkCheckmkPulse
|
|||||||
Host = ReadAttribute(node, "host"),
|
Host = ReadAttribute(node, "host"),
|
||||||
Port = ReadInt(node, "port", 1, 65535),
|
Port = ReadInt(node, "port", 1, 65535),
|
||||||
Enabled = ReadBool(node, "enabled"),
|
Enabled = ReadBool(node, "enabled"),
|
||||||
AutoDiscovered = ReadBool(node, "autoDiscovered")
|
AutoDiscovered = ReadBool(node, "autoDiscovered"),
|
||||||
|
// Das optionale Attribut haelt bestehende Kataloge der Version 1 lesbar.
|
||||||
|
BestEffort = ReadOptionalBool(node, "bestEffort", false)
|
||||||
};
|
};
|
||||||
ValidateEntry(entry);
|
ValidateEntry(entry);
|
||||||
catalog.Entries.Add(entry);
|
catalog.Entries.Add(entry);
|
||||||
@@ -126,6 +139,10 @@ namespace BizTalkCheckmkPulse
|
|||||||
return catalog;
|
return catalog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validiert und ersetzt den Endpoint-Katalog atomar.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="catalog">Zu persistierender geheimnisfreier Katalog.</param>
|
||||||
public void Write(EndpointCatalog catalog)
|
public void Write(EndpointCatalog catalog)
|
||||||
{
|
{
|
||||||
if (catalog == null)
|
if (catalog == null)
|
||||||
@@ -170,7 +187,8 @@ namespace BizTalkCheckmkPulse
|
|||||||
new XAttribute("host", entry.Host),
|
new XAttribute("host", entry.Host),
|
||||||
new XAttribute("port", entry.Port),
|
new XAttribute("port", entry.Port),
|
||||||
new XAttribute("enabled", entry.Enabled),
|
new XAttribute("enabled", entry.Enabled),
|
||||||
new XAttribute("autoDiscovered", entry.AutoDiscovered)));
|
new XAttribute("autoDiscovered", entry.AutoDiscovered),
|
||||||
|
new XAttribute("bestEffort", entry.BestEffort)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var document = new XDocument(new XDeclaration("1.0", "utf-8", null), root);
|
var document = new XDocument(new XDeclaration("1.0", "utf-8", null), root);
|
||||||
@@ -284,5 +302,29 @@ namespace BizTalkCheckmkPulse
|
|||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Liest ein optionales boolesches Attribut mit rueckwaertskompatiblem Standardwert.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="element">XML-Element mit dem optionalen Attribut.</param>
|
||||||
|
/// <param name="name">Name des Attributs.</param>
|
||||||
|
/// <param name="defaultValue">Rueckgabewert, wenn das Attribut fehlt.</param>
|
||||||
|
/// <returns>Gelesener Wert oder der angegebene Standardwert.</returns>
|
||||||
|
private static bool ReadOptionalBool(XElement element, string name, bool defaultValue)
|
||||||
|
{
|
||||||
|
var attribute = element.Attribute(name);
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool value;
|
||||||
|
if (!bool.TryParse(attribute.Value.Trim(), out value))
|
||||||
|
{
|
||||||
|
throw new InvalidDataException("Endpoint catalog boolean is invalid: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,21 @@ namespace BizTalkCheckmkPulse
|
|||||||
private readonly MonitoringOptions _options;
|
private readonly MonitoringOptions _options;
|
||||||
private readonly FileLogger _logger;
|
private readonly FileLogger _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialisiert die Endpoint-Pruefung mit Laufzeitgrenzen und Dateiprotokoll.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">Validierte Monitoring-Konfiguration.</param>
|
||||||
|
/// <param name="logger">Gemeinsames Provider-Dateiprotokoll.</param>
|
||||||
public EndpointConnectivityProbe(MonitoringOptions options, FileLogger logger)
|
public EndpointConnectivityProbe(MonitoringOptions options, FileLogger logger)
|
||||||
{
|
{
|
||||||
_options = options;
|
_options = options;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronisiert den Katalog und ergaenzt das Gesamtergebnis um Socket-Probes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">Bereits mit aktuellen BizTalk-Artefakten gefuelltes Ergebnis.</param>
|
||||||
public void Query(ProbeResult result)
|
public void Query(ProbeResult result)
|
||||||
{
|
{
|
||||||
var state = result.EndpointConnectivity;
|
var state = result.EndpointConnectivity;
|
||||||
@@ -62,7 +71,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)
|
||||||
@@ -140,7 +150,13 @@ namespace BizTalkCheckmkPulse
|
|||||||
foreach (var probeResult in probeResults)
|
foreach (var probeResult in probeResults)
|
||||||
{
|
{
|
||||||
state.Results.Add(probeResult);
|
state.Results.Add(probeResult);
|
||||||
if (!probeResult.Available)
|
if (!probeResult.Available && probeResult.Endpoint.BestEffort)
|
||||||
|
{
|
||||||
|
// DATABASE-/WCF-SAP-Ziele sind bewusst Diagnose-Probes: Ein
|
||||||
|
// Fehlschlag darf weder Tageslog noch Checkmk mit Alarmen fluten.
|
||||||
|
state.BestEffortIgnoredFailures++;
|
||||||
|
}
|
||||||
|
else if (!probeResult.Available)
|
||||||
{
|
{
|
||||||
_logger.Warning(
|
_logger.Warning(
|
||||||
"Endpoint unavailable. endpoint=" + Display(probeResult.Endpoint)
|
"Endpoint unavailable. endpoint=" + Display(probeResult.Endpoint)
|
||||||
@@ -156,6 +172,13 @@ namespace BizTalkCheckmkPulse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fuehrt manuelle Overrides mit den aktuell automatisch erkannten Zielen zusammen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="existing">Vorhandener Katalog oder <see langword="null"/>.</param>
|
||||||
|
/// <param name="candidates">Aktuelle BizTalk-Send-/Receive-Transporte.</param>
|
||||||
|
/// <param name="synchronizedUtc">UTC-Zeitpunkt des vollstaendigen Abgleichs.</param>
|
||||||
|
/// <returns>Neuer, geheimnisfreier Endpoint-Katalog.</returns>
|
||||||
internal static EndpointCatalog SynchronizeCatalog(
|
internal static EndpointCatalog SynchronizeCatalog(
|
||||||
EndpointCatalog existing,
|
EndpointCatalog existing,
|
||||||
IEnumerable<EndpointCandidate> candidates,
|
IEnumerable<EndpointCandidate> candidates,
|
||||||
@@ -222,6 +245,10 @@ namespace BizTalkCheckmkPulse
|
|||||||
/// Ermittelt aus aktuellen WMI-Kandidaten und manuellen Overrides die in diesem Lauf zu pruefenden Ziele.
|
/// Ermittelt aus aktuellen WMI-Kandidaten und manuellen Overrides die in diesem Lauf zu pruefenden Ziele.
|
||||||
/// Automatisch erkannte Katalogeintraege werden bewusst nicht als veraltete Laufzeitquelle verwendet.
|
/// Automatisch erkannte Katalogeintraege werden bewusst nicht als veraltete Laufzeitquelle verwendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="catalog">Gueltiger lokaler Endpoint-Katalog.</param>
|
||||||
|
/// <param name="candidates">Aktuelle aktive und inaktive BizTalk-Transporte.</param>
|
||||||
|
/// <param name="state">Zu aktualisierende Laufzeitzaehler und Aufloesungsdiagnosen.</param>
|
||||||
|
/// <returns>Aktuell aktive, deduplizierte Katalogeintraege.</returns>
|
||||||
internal static EndpointCatalogEntry[] ResolveActiveEndpoints(
|
internal static EndpointCatalogEntry[] ResolveActiveEndpoints(
|
||||||
EndpointCatalog catalog,
|
EndpointCatalog catalog,
|
||||||
IEnumerable<EndpointCandidate> candidates,
|
IEnumerable<EndpointCandidate> candidates,
|
||||||
@@ -283,10 +310,12 @@ namespace BizTalkCheckmkPulse
|
|||||||
x.Enabled
|
x.Enabled
|
||||||
&& !string.Equals(x.ArtifactType, "Manual", StringComparison.OrdinalIgnoreCase)
|
&& !string.Equals(x.ArtifactType, "Manual", StringComparison.OrdinalIgnoreCase)
|
||||||
&& !activeKeys.Contains(x.Key));
|
&& !activeKeys.Contains(x.Key));
|
||||||
return selected
|
var resolved = selected
|
||||||
.GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase)
|
.GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(x => x.First())
|
.Select(x => x.First())
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
state.BestEffortActive = resolved.Count(x => x.BestEffort);
|
||||||
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -355,6 +384,13 @@ namespace BizTalkCheckmkPulse
|
|||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Berechnet die theoretische Obergrenze fuer vollstaendig timeoutende Zielgruppen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetCount">Anzahl eindeutiger Socket-Ziele.</param>
|
||||||
|
/// <param name="concurrency">Maximale parallele Probes.</param>
|
||||||
|
/// <param name="timeoutMilliseconds">Timeout je Probe in Millisekunden.</param>
|
||||||
|
/// <returns>Theoretische Worst-Case-Dauer in Millisekunden.</returns>
|
||||||
internal static long CalculateWorstCaseProbeMilliseconds(int targetCount, int concurrency, int timeoutMilliseconds)
|
internal static long CalculateWorstCaseProbeMilliseconds(int targetCount, int concurrency, int timeoutMilliseconds)
|
||||||
{
|
{
|
||||||
if (targetCount <= 0)
|
if (targetCount <= 0)
|
||||||
@@ -464,6 +500,11 @@ namespace BizTalkCheckmkPulse
|
|||||||
: "SocketError=" + socket.SocketErrorCode + ": " + socket.Message;
|
: "SocketError=" + socket.SocketErrorCode + ": " + socket.Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formatiert ein geheimnisfreies Socket-Ziel fuer Checkmk und Dateilog.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">Zu formatierender Katalogeintrag.</param>
|
||||||
|
/// <returns>Artefakt, Host, Port und Protokoll ohne URI-Geheimnisse.</returns>
|
||||||
internal static string Display(EndpointCatalogEntry endpoint)
|
internal static string Display(EndpointCatalogEntry endpoint)
|
||||||
{
|
{
|
||||||
var artifact = string.IsNullOrWhiteSpace(endpoint.ApplicationName)
|
var artifact = string.IsNullOrWhiteSpace(endpoint.ApplicationName)
|
||||||
|
|||||||
@@ -305,7 +305,20 @@ namespace BizTalkCheckmkPulse
|
|||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
public bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public bool AutoDiscovered { get; set; }
|
public bool AutoDiscovered { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gibt an, dass ein automatisch abgeleiteter Adaptertest nur Diagnosewert besitzt
|
||||||
|
/// und ein Fehlschlag deshalb keinen Checkmk-Alarm ausloest.
|
||||||
|
/// </summary>
|
||||||
|
public bool BestEffort { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Erstellt den stabilen, adressunabhaengigen Schluessel eines BizTalk-Transports.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="artifactType">Typ des BizTalk-Artefakts.</param>
|
||||||
|
/// <param name="applicationName">Name der BizTalk-Anwendung.</param>
|
||||||
|
/// <param name="artifactName">Name des Send Ports oder der Receive Location.</param>
|
||||||
|
/// <param name="transportRole">Primaer-, Sekundaer- oder Inbound-Rolle.</param>
|
||||||
|
/// <returns>Normalisierter Katalogschluessel.</returns>
|
||||||
public static string BuildKey(string artifactType, string applicationName, string artifactName, string transportRole)
|
public static string BuildKey(string artifactType, string applicationName, string artifactName, string transportRole)
|
||||||
{
|
{
|
||||||
return NormalizeKeyPart(artifactType)
|
return NormalizeKeyPart(artifactType)
|
||||||
@@ -371,6 +384,14 @@ namespace BizTalkCheckmkPulse
|
|||||||
public int SkippedInactive { get; set; }
|
public int SkippedInactive { get; set; }
|
||||||
public int UnresolvedActive { get; set; }
|
public int UnresolvedActive { get; set; }
|
||||||
public int ExpectedNonProbeableActive { get; set; }
|
public int ExpectedNonProbeableActive { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Anzahl der automatisch abgeleiteten, nicht alarmierenden Adapterziele.
|
||||||
|
/// </summary>
|
||||||
|
public int BestEffortActive { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Anzahl fehlgeschlagener Best-Effort-Probes im aktuellen Lauf.
|
||||||
|
/// </summary>
|
||||||
|
public int BestEffortIgnoredFailures { get; set; }
|
||||||
public int ManualOverridesActive { get; set; }
|
public int ManualOverridesActive { get; set; }
|
||||||
public int UniqueTargets { get; set; }
|
public int UniqueTargets { get; set; }
|
||||||
public long ProbeDurationMilliseconds { get; set; }
|
public long ProbeDurationMilliseconds { get; set; }
|
||||||
|
|||||||
@@ -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,15 +111,13 @@ 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 && !x.Endpoint.BestEffort)
|
||||||
|
+ " endpoints_best_effort_ignored="
|
||||||
|
+ result.EndpointConnectivity.BestEffortIgnoredFailures
|
||||||
+ " endpoints_unresolved="
|
+ " endpoints_unresolved="
|
||||||
+ result.EndpointConnectivity.UnresolvedActive
|
+ result.EndpointConnectivity.UnresolvedActive
|
||||||
+ " endpoints_expected_non_socket="
|
+ " endpoints_expected_non_socket="
|
||||||
@@ -137,6 +147,36 @@ 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
|
||||||
|
+ " acceptedUnknown=" + validation.AcceptedUnknownLines.Count);
|
||||||
|
foreach (var line in validation.AcceptedUnknownLines)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
"RUNTIME_VALIDATION_WARNING: Installation technisch valide; Monitoringzustand bleibt sichtbar: "
|
||||||
|
+ line);
|
||||||
|
}
|
||||||
|
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.7.0")]
|
||||||
[assembly: AssemblyFileVersion("2.2.3.0")]
|
[assembly: AssemblyFileVersion("2.2.7.0")]
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
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;
|
||||||
|
string[] unknownServiceLines;
|
||||||
|
var expectedServices = formatter.FormatSelfTest().Select(ExtractServiceName).ToArray();
|
||||||
|
if (!ContainsExactStableContract(
|
||||||
|
snapshot.Lines,
|
||||||
|
expectedServices,
|
||||||
|
out unknownServiceLines,
|
||||||
|
out serviceError))
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed(serviceError);
|
||||||
|
}
|
||||||
|
|
||||||
|
var acceptedEndpointUnknownName = options.ServiceName("Endpoint Reachability");
|
||||||
|
var blockingUnknown = unknownServiceLines
|
||||||
|
.Where(x => !options.ProbeEndpointConnectivity
|
||||||
|
|| !string.Equals(
|
||||||
|
ExtractServiceName(x),
|
||||||
|
acceptedEndpointUnknownName,
|
||||||
|
StringComparison.Ordinal))
|
||||||
|
.Select(ExtractServiceName)
|
||||||
|
.OrderBy(x => x, StringComparer.Ordinal)
|
||||||
|
.ToArray();
|
||||||
|
if (blockingUnknown.Length != 0)
|
||||||
|
{
|
||||||
|
return RuntimeValidationResult.Failed(
|
||||||
|
"Snapshot enthaelt blockierende UNKNOWN-Zustaende in Kernservices. UNKNOWN=["
|
||||||
|
+ string.Join(", ", blockingUnknown) + "].");
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
unknownServiceLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool ContainsExactStableContract(
|
||||||
|
IEnumerable<string> lines,
|
||||||
|
IEnumerable<string> expectedServices,
|
||||||
|
out string[] unknownServiceLines,
|
||||||
|
out string error)
|
||||||
|
{
|
||||||
|
unknownServiceLines = new string[0];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
unknownServiceLines = (lines ?? Enumerable.Empty<string>())
|
||||||
|
.Where(x => x != null && x.StartsWith("3 ", StringComparison.Ordinal))
|
||||||
|
.Where(x => expected.Contains(ExtractServiceName(x), StringComparer.Ordinal))
|
||||||
|
.OrderBy(ExtractServiceName, StringComparer.Ordinal)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
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 IReadOnlyList<string> AcceptedUnknownLines { get; private set; }
|
||||||
|
|
||||||
|
public static RuntimeValidationResult Success(
|
||||||
|
DateTime generatedUtc,
|
||||||
|
DateTime? catalogUtc,
|
||||||
|
int serviceCount,
|
||||||
|
IEnumerable<string> acceptedUnknownLines)
|
||||||
|
{
|
||||||
|
return new RuntimeValidationResult
|
||||||
|
{
|
||||||
|
IsSuccess = true,
|
||||||
|
GeneratedUtc = generatedUtc,
|
||||||
|
CatalogSynchronizedUtc = catalogUtc,
|
||||||
|
StableServiceCount = serviceCount,
|
||||||
|
AcceptedUnknownLines = (acceptedUnknownLines ?? Enumerable.Empty<string>()).ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RuntimeValidationResult Failed(string error)
|
||||||
|
{
|
||||||
|
return new RuntimeValidationResult
|
||||||
|
{
|
||||||
|
Error = error,
|
||||||
|
AcceptedUnknownLines = new string[0]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,21 +30,37 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded);
|
Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded);
|
||||||
Run("EndpointAddressesResolveWithoutSecrets", EndpointAddressesResolveWithoutSecrets);
|
Run("EndpointAddressesResolveWithoutSecrets", EndpointAddressesResolveWithoutSecrets);
|
||||||
Run("AdapterSpecificEndpointAddressesResolve", AdapterSpecificEndpointAddressesResolve);
|
Run("AdapterSpecificEndpointAddressesResolve", AdapterSpecificEndpointAddressesResolve);
|
||||||
|
Run("SchedulerDatabaseAndSapEndpointsAreClassifiedSafely", SchedulerDatabaseAndSapEndpointsAreClassifiedSafely);
|
||||||
Run("EndpointClassificationSeparatesExpectedAndUnresolved", EndpointClassificationSeparatesExpectedAndUnresolved);
|
Run("EndpointClassificationSeparatesExpectedAndUnresolved", EndpointClassificationSeparatesExpectedAndUnresolved);
|
||||||
Run("EndpointCatalogPreservesManualOverrides", EndpointCatalogPreservesManualOverrides);
|
Run("EndpointCatalogPreservesManualOverrides", EndpointCatalogPreservesManualOverrides);
|
||||||
Run("EndpointRuntimeUsesCurrentAddressAndManualOverrides", EndpointRuntimeUsesCurrentAddressAndManualOverrides);
|
Run("EndpointRuntimeUsesCurrentAddressAndManualOverrides", EndpointRuntimeUsesCurrentAddressAndManualOverrides);
|
||||||
Run("EndpointCatalogRoundTrip", EndpointCatalogRoundTrip);
|
Run("EndpointCatalogRoundTrip", EndpointCatalogRoundTrip);
|
||||||
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
|
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
|
||||||
|
Run("BestEffortEndpointFailuresRemainNonAlerting", BestEffortEndpointFailuresRemainNonAlerting);
|
||||||
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("InstallerAcceptsLegacyEightServiceSelfTest", InstallerAcceptsLegacyEightServiceSelfTest);
|
||||||
|
Run("InstallerRequiresNineServicesForCandidate", InstallerRequiresNineServicesForCandidate);
|
||||||
|
Run("InstallerAllowsAdditiveServiceContract", InstallerAllowsAdditiveServiceContract);
|
||||||
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("RuntimeValidationAllowsEndpointUnknownAsWarning", RuntimeValidationAllowsEndpointUnknownAsWarning);
|
||||||
|
Run("RuntimeValidationRejectsEndpointUnknownWhenProbeDisabled", RuntimeValidationRejectsEndpointUnknownWhenProbeDisabled);
|
||||||
|
Run("RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog", RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog);
|
||||||
|
Run("ForceEndpointRefreshRequiresProviderMode", ForceEndpointRefreshRequiresProviderMode);
|
||||||
|
Run("RuntimeValidationArgumentsAreParsedStrictly", RuntimeValidationArgumentsAreParsedStrictly);
|
||||||
|
Run("TaskCompletionRequiresFreshSuccessfulRun", TaskCompletionRequiresFreshSuccessfulRun);
|
||||||
|
Run("TaskFailureIsDetectedImmediately", TaskFailureIsDetectedImmediately);
|
||||||
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
||||||
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
||||||
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
||||||
@@ -337,6 +353,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$");
|
||||||
@@ -455,6 +472,73 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
AssertEqual(8080, endpoint.Port, "wildcard listener port");
|
AssertEqual(8080, endpoint.Port, "wildcard listener port");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SchedulerDatabaseAndSapEndpointsAreClassifiedSafely()
|
||||||
|
{
|
||||||
|
var candidate = new EndpointCandidate
|
||||||
|
{
|
||||||
|
ArtifactType = "ReceiveLocation",
|
||||||
|
ApplicationName = "Integration",
|
||||||
|
ArtifactName = "RL Scheduler",
|
||||||
|
TransportRole = "Inbound",
|
||||||
|
AdapterName = "Schedule",
|
||||||
|
Address = "schedule://weekday/06:00",
|
||||||
|
Active = true
|
||||||
|
};
|
||||||
|
|
||||||
|
AssertEqual(
|
||||||
|
EndpointResolutionStatus.ExpectedNonProbeable,
|
||||||
|
EndpointAddressParser.Analyze(candidate).Status,
|
||||||
|
"scheduler must be excluded");
|
||||||
|
|
||||||
|
candidate.ArtifactType = "SendPort";
|
||||||
|
candidate.ArtifactName = "SP Oracle";
|
||||||
|
candidate.TransportRole = "Primary";
|
||||||
|
candidate.AdapterName = "DATABASE";
|
||||||
|
candidate.Address = "Database://oracle01/SAPUSER/Oracle";
|
||||||
|
var resolution = EndpointAddressParser.Analyze(candidate);
|
||||||
|
AssertEqual(EndpointResolutionStatus.Probeable, resolution.Status, "Oracle DATABASE classification");
|
||||||
|
AssertEqual("oracle01", resolution.Entry.Host, "Oracle DATABASE host");
|
||||||
|
AssertEqual(1521, resolution.Entry.Port, "Oracle DATABASE default port");
|
||||||
|
Assert(resolution.Entry.BestEffort, "Oracle DATABASE probe must be best effort");
|
||||||
|
|
||||||
|
candidate.Address = "Database://oracle01:1526/SAPUSER/Oracle";
|
||||||
|
resolution = EndpointAddressParser.Analyze(candidate);
|
||||||
|
AssertEqual(1526, resolution.Entry.Port, "Oracle DATABASE explicit port");
|
||||||
|
|
||||||
|
candidate.Address = "Database://sql01/Lookup/SQLServer";
|
||||||
|
resolution = EndpointAddressParser.Analyze(candidate);
|
||||||
|
AssertEqual(
|
||||||
|
EndpointResolutionStatus.ExpectedNonProbeable,
|
||||||
|
resolution.Status,
|
||||||
|
"non-Oracle DATABASE endpoint must not cause UNKNOWN");
|
||||||
|
|
||||||
|
candidate.ArtifactType = "ReceiveLocation";
|
||||||
|
candidate.ArtifactName = "RL SAP";
|
||||||
|
candidate.TransportRole = "Inbound";
|
||||||
|
// WMI kann einen WCF-SAP-Port lediglich als WCF-Custom ausweisen;
|
||||||
|
// das sap://-Schema muss deshalb fuer die Klassifizierung genuegen.
|
||||||
|
candidate.AdapterName = "WCF-Custom";
|
||||||
|
candidate.Address = "sap://Client=800;lang=DE@A/sap-app/00?ListenerGwHost=sap-gateway.example&ListenerGwServ=SAPGW00&ListenerProgramId=SECRET";
|
||||||
|
resolution = EndpointAddressParser.Analyze(candidate);
|
||||||
|
AssertEqual(EndpointResolutionStatus.Probeable, resolution.Status, "WCF-SAP listener classification");
|
||||||
|
AssertEqual("sap-gateway.example", resolution.Entry.Host, "WCF-SAP gateway host");
|
||||||
|
AssertEqual(3300, resolution.Entry.Port, "WCF-SAP sapgw00 port");
|
||||||
|
Assert(resolution.Entry.BestEffort, "WCF-SAP probe must be best effort");
|
||||||
|
Assert(resolution.Entry.Host.IndexOf("SECRET", StringComparison.OrdinalIgnoreCase) < 0, "SAP Program ID must not enter catalog");
|
||||||
|
|
||||||
|
candidate.Address = "sap://Client=800;lang=DE@A/sap-app/00?SAPROUTER=%2fH%2fsap-router.example%2fS%2f3400%2fH%2fsap-gateway.example&ListenerGwHost=sap-gateway.example&ListenerGwServ=sapgw00";
|
||||||
|
resolution = EndpointAddressParser.Analyze(candidate);
|
||||||
|
AssertEqual("sap-router.example", resolution.Entry.Host, "SAProuter first hop host");
|
||||||
|
AssertEqual(3400, resolution.Entry.Port, "SAProuter explicit port");
|
||||||
|
|
||||||
|
candidate.Address = "sap://Client=800;lang=DE@D/DESTINATION";
|
||||||
|
resolution = EndpointAddressParser.Analyze(candidate);
|
||||||
|
AssertEqual(
|
||||||
|
EndpointResolutionStatus.ExpectedNonProbeable,
|
||||||
|
resolution.Status,
|
||||||
|
"destination-based WCF-SAP endpoint must not cause UNKNOWN");
|
||||||
|
}
|
||||||
|
|
||||||
private static void EndpointClassificationSeparatesExpectedAndUnresolved()
|
private static void EndpointClassificationSeparatesExpectedAndUnresolved()
|
||||||
{
|
{
|
||||||
var candidate = new EndpointCandidate
|
var candidate = new EndpointCandidate
|
||||||
@@ -602,6 +686,33 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
Assert(line.IndexOf("up.example.test", StringComparison.Ordinal) < 0, "reachable host must not clutter output");
|
Assert(line.IndexOf("up.example.test", StringComparison.Ordinal) < 0, "reachable host must not clutter output");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void BestEffortEndpointFailuresRemainNonAlerting()
|
||||||
|
{
|
||||||
|
var result = new ProbeResult();
|
||||||
|
result.EndpointConnectivity.RuntimeStateAvailable = true;
|
||||||
|
result.EndpointConnectivity.CatalogAvailable = true;
|
||||||
|
result.EndpointConnectivity.RefreshSucceeded = true;
|
||||||
|
result.EndpointConnectivity.Configured = 1;
|
||||||
|
result.EndpointConnectivity.Active = 1;
|
||||||
|
result.EndpointConnectivity.BestEffortActive = 1;
|
||||||
|
var endpoint = TestEndpoint("SAP Listener", "sap-gateway.example");
|
||||||
|
endpoint.AdapterName = "WCF-SAP";
|
||||||
|
endpoint.Port = 3300;
|
||||||
|
endpoint.BestEffort = true;
|
||||||
|
result.EndpointConnectivity.Results.Add(new EndpointProbeResult
|
||||||
|
{
|
||||||
|
Endpoint = endpoint,
|
||||||
|
Available = false,
|
||||||
|
Failure = "TCP timeout"
|
||||||
|
});
|
||||||
|
|
||||||
|
var line = FindServiceLine(result, "Endpoint Reachability");
|
||||||
|
Assert(line.StartsWith("0 \"BizTalk Endpoint Reachability\"", StringComparison.Ordinal), "best-effort failure must remain OK");
|
||||||
|
Assert(line.Contains("biztalk_endpoints_failed=0"), "best-effort failure must not increment failed metric");
|
||||||
|
Assert(line.Contains("biztalk_endpoints_best_effort_ignored=1"), "ignored best-effort metric missing");
|
||||||
|
Assert(line.IndexOf("unavailable=", StringComparison.OrdinalIgnoreCase) < 0, "best-effort target must not appear as unavailable");
|
||||||
|
}
|
||||||
|
|
||||||
private static void EndpointOutputExplainsOnlyRealResolutionGaps()
|
private static void EndpointOutputExplainsOnlyRealResolutionGaps()
|
||||||
{
|
{
|
||||||
var result = new ProbeResult();
|
var result = new ProbeResult();
|
||||||
@@ -637,7 +748,9 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
ActiveCandidates = 150,
|
ActiveCandidates = 150,
|
||||||
UnresolvedCandidates = 7
|
UnresolvedCandidates = 7
|
||||||
};
|
};
|
||||||
catalog.Entries.Add(TestEndpoint("Orders", "api.example.test"));
|
var storedEndpoint = TestEndpoint("Orders", "api.example.test");
|
||||||
|
storedEndpoint.BestEffort = true;
|
||||||
|
catalog.Entries.Add(storedEndpoint);
|
||||||
var store = new EndpointCatalogStore(path, 1048576, 100);
|
var store = new EndpointCatalogStore(path, 1048576, 100);
|
||||||
store.Write(catalog);
|
store.Write(catalog);
|
||||||
var loaded = store.Read("ACC");
|
var loaded = store.Read("ACC");
|
||||||
@@ -645,8 +758,18 @@ namespace BizTalkCheckmkPulse.Tests
|
|||||||
AssertEqual(1, loaded.Entries.Count, "catalog entry count");
|
AssertEqual(1, loaded.Entries.Count, "catalog entry count");
|
||||||
AssertEqual("api.example.test", loaded.Entries[0].Host, "catalog host");
|
AssertEqual("api.example.test", loaded.Entries[0].Host, "catalog host");
|
||||||
AssertEqual(443, loaded.Entries[0].Port, "catalog port");
|
AssertEqual(443, loaded.Entries[0].Port, "catalog port");
|
||||||
|
Assert(loaded.Entries[0].BestEffort, "catalog best-effort marker");
|
||||||
AssertEqual(7, loaded.UnresolvedCandidates, "catalog unresolved candidate count");
|
AssertEqual(7, loaded.UnresolvedCandidates, "catalog unresolved candidate count");
|
||||||
Assert(File.ReadAllText(path).IndexOf("top-secret", StringComparison.OrdinalIgnoreCase) < 0, "catalog must not contain URI secrets");
|
Assert(File.ReadAllText(path).IndexOf("top-secret", StringComparison.OrdinalIgnoreCase) < 0, "catalog must not contain URI secrets");
|
||||||
|
|
||||||
|
// Vorhandene Kataloge der Formatversion 1 besitzen das neue optionale
|
||||||
|
// Attribut noch nicht und muessen beim Update weiterhin lesbar bleiben.
|
||||||
|
var legacyXml = File.ReadAllText(path)
|
||||||
|
.Replace(" bestEffort=\"true\"", string.Empty)
|
||||||
|
.Replace(" bestEffort=\"True\"", string.Empty);
|
||||||
|
File.WriteAllText(path, legacyXml, new UTF8Encoding(false));
|
||||||
|
loaded = store.Read("ACC");
|
||||||
|
Assert(!loaded.Entries[0].BestEffort, "legacy catalog best-effort default");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -690,8 +813,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" };
|
||||||
@@ -758,6 +924,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;
|
||||||
@@ -765,7 +976,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 +989,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 +1013,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 +1033,274 @@ 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 core service must fail runtime acceptance");
|
||||||
|
Assert(result.Error.Contains("UNKNOWN=[BizTalk Platform]"), "UNKNOWN core service reason missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationAllowsEndpointUnknownAsWarning()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
var endpointIndex = Array.FindIndex(
|
||||||
|
lines,
|
||||||
|
x => x.Contains("\"BizTalk Endpoint Reachability\""));
|
||||||
|
Assert(endpointIndex >= 0, "endpoint service missing from self-test contract");
|
||||||
|
lines[endpointIndex] = "3 \"BizTalk Endpoint Reachability\" "
|
||||||
|
+ "biztalk_endpoints_unresolved=1;;1;0 Endpoint reachability incomplete; unresolved=1.";
|
||||||
|
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
boundary.AddSeconds(5),
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
true,
|
||||||
|
lines);
|
||||||
|
|
||||||
|
Assert(result.IsSuccess, "endpoint UNKNOWN must be accepted as operational warning: " + result.Error);
|
||||||
|
AssertEqual(1, result.AcceptedUnknownLines.Count, "accepted endpoint UNKNOWN count");
|
||||||
|
Assert(result.AcceptedUnknownLines[0].Contains("unresolved=1"), "endpoint UNKNOWN detail must be retained");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RuntimeValidationRejectsEndpointUnknownWhenProbeDisabled()
|
||||||
|
{
|
||||||
|
var boundary = DateTime.UtcNow.AddMinutes(-1);
|
||||||
|
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
|
||||||
|
var endpointIndex = Array.FindIndex(
|
||||||
|
lines,
|
||||||
|
x => x.Contains("\"BizTalk Endpoint Reachability\""));
|
||||||
|
lines[endpointIndex] = "3 \"BizTalk Endpoint Reachability\" - Unexpected UNKNOWN while probe is disabled.";
|
||||||
|
|
||||||
|
var result = ValidateRuntimeArtifacts(
|
||||||
|
boundary,
|
||||||
|
boundary.AddSeconds(10),
|
||||||
|
null,
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
"BEW\\t231bizmon",
|
||||||
|
false,
|
||||||
|
lines);
|
||||||
|
|
||||||
|
Assert(!result.IsSuccess, "endpoint UNKNOWN must be rejected when endpoint probing is disabled");
|
||||||
|
Assert(result.Error.Contains("UNKNOWN=[BizTalk Endpoint Reachability]"), "disabled-probe UNKNOWN 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 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(
|
||||||
|
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