Compare commits
3
Commits
448be8dc49
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f8e8034e6 | ||
|
|
951a5e72d1 | ||
|
|
cc1e59916a |
+42
-4
@@ -414,11 +414,48 @@ dem echten Collector-Konto und erzwingt einen vollständigen Endpoint-
|
||||
Katalogabgleich. Der Installer wartet begrenzt auf `LastTaskResult=0` und lässt
|
||||
anschließend die installierte Produkt-EXE Snapshot und Katalog mit den normalen
|
||||
Produktparsern validieren. Der Snapshot muss aus diesem Lauf stammen, die
|
||||
erwartete Identität und alle neun eindeutigen Services ohne `UNKNOWN`
|
||||
enthalten. Erst dann wird der normale Minutentask registriert und das Backup
|
||||
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
|
||||
übergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle;
|
||||
es wird weder in einer Prozesskommandozeile noch in Config oder Log
|
||||
@@ -618,8 +655,9 @@ Automatisiert:
|
||||
- erzwungener Installations-Katalogabgleich wird unabhängig vom Katalogalter
|
||||
ausgeführt,
|
||||
- Runtime-Abnahme weist alten/manipulierten Snapshot, falsche Identität,
|
||||
fehlenden Service, `UNKNOWN`, alten/ungültigen Katalog und fehlerhaften oder
|
||||
nicht rechtzeitig beendeten Tasklauf zurück,
|
||||
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,
|
||||
- SHA-256-Manipulation 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
|
||||
TCP-Verbindungsaufbau zu Host und Port geprüft. Explizite
|
||||
`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
|
||||
BizTalk-Nachrichten gesendet. Der Check validiert DNS, Netzwerkpfad,
|
||||
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.
|
||||
- Provider und Consumer protokollieren in tägliche Dateien; die Aufbewahrung
|
||||
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
|
||||
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 jede aktive BizTalk-Adresse ist selbst ein Socket-Ziel. Dynamische Send
|
||||
Ports, lokale Pfade, SMTP-Empfänger, Named Pipes und relative HTTP-/WCF-
|
||||
Receive-Listener werden deshalb als erwartbar nicht socket-prüfbar behandelt
|
||||
und erzeugen kein `UNKNOWN`. Nur eine aktive statische 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.
|
||||
Ports, lokale Pfade, SMTP-Empfänger, Named Pipes, relative HTTP-/WCF-
|
||||
Receive-Listener, Scheduling-Komponenten sowie nicht sicher ableitbare
|
||||
DATABASE-/WCF-SAP-Adressen werden deshalb als erwartbar nicht socket-prüfbar
|
||||
behandelt und erzeugen kein `UNKNOWN`. Nur eine andere aktive statische
|
||||
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
|
||||
`%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml`. Fehlt er, wird er beim
|
||||
@@ -154,6 +169,10 @@ Beispielausgaben: [docs/ExampleOutput.md](docs/ExampleOutput.md)
|
||||
Endpoint-Katalog, manuelle Overrides und Protokollgrenzen:
|
||||
[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
|
||||
|
||||
Build-Host:
|
||||
@@ -263,27 +282,36 @@ als `UNKNOWN`.
|
||||
Ein gMSA kann über die Checkbox ohne Kennworteingabe installiert werden; die
|
||||
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
|
||||
wird,
|
||||
1. prüft Paket und Staging zwingend auf neun Self-Test-Zeilen, bevor der
|
||||
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
|
||||
vorhandene AppSettings; neue Keys erhalten ihre neuen Defaults. Die alten
|
||||
unveränderten Endpoint-Defaults `12`/`500` werden auf `16`/`100` migriert,
|
||||
3. vergleicht installierten und neuen Checkmk-Servicevertrag; ein
|
||||
unbestätigter Rename stoppt vor jeder Umschaltung,
|
||||
3. vergleicht installierten und neuen Checkmk-Servicevertrag. Rein additive
|
||||
Services werden zugelassen und als Discovery-Hinweis gemeldet; entfernte
|
||||
oder umbenannte Services stoppen ohne ausdrückliche Bestätigung vor jeder
|
||||
Umschaltung,
|
||||
4. schaltet die Programmdateien per Verzeichniswechsel um,
|
||||
5. startet einen triggerlosen Provider-Abnahmelauf unter dem echten
|
||||
Collector-Konto und erzwingt einen vollständigen Endpoint-Katalogabgleich,
|
||||
6. wartet höchstens vier Minuten auf `LastTaskResult=0` und validiert danach
|
||||
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 ohne `UNKNOWN`
|
||||
sowie den frischen, maschinen- und umgebungsgebundenen Endpoint-Katalog,
|
||||
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,
|
||||
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
|
||||
@@ -306,8 +334,20 @@ Provider-Log:
|
||||
```cmd
|
||||
dir /o-d "%ProgramData%\BizTalkCheckmkPulse\logs"
|
||||
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:
|
||||
|
||||
```cmd
|
||||
@@ -410,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. |
|
||||
| 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 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. |
|
||||
| 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. |
|
||||
|
||||
@@ -28,14 +28,41 @@ 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
|
||||
eine Checkmk Service Discovery erforderlich.
|
||||
|
||||
Ab Version 2.2.3 vergleicht der Installer bei jedem Update die exakten
|
||||
Servicenamen der installierten und der vorbereiteten Version. Neue, entfernte
|
||||
oder umbenannte Services führen **vor dem Stoppen des Tasks** zu einem
|
||||
Sicherheitsstopp. Die Installation darf nur über die Checkbox
|
||||
Ab Version 2.2.5 vergleicht der Installer bei jedem Update die exakten
|
||||
Servicenamen der installierten und der vorbereiteten Version. Rein neue
|
||||
Services sind abwärtskompatibel, blockieren das Update nicht und werden als
|
||||
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**
|
||||
fortgesetzt werden. Der Installer zeigt dabei die entfernten und neuen Namen
|
||||
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,
|
||||
@@ -45,7 +72,9 @@ Version 2.2.4 nimmt die umgeschaltete Installation zusätzlich vollständig ab:
|
||||
- Snapshot muss nach Beginn des Abnahmelaufs erzeugt, SHA-256-valid, für die
|
||||
aktuelle Maschine und vom erwarteten Collector-Konto geschrieben sein,
|
||||
- alle neun stabilen Services müssen eindeutig vorhanden sein; `WARN` und
|
||||
`CRIT` sind reale Betriebszustände, `UNKNOWN` blockiert die Installation,
|
||||
`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.
|
||||
|
||||
@@ -55,6 +84,16 @@ 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
|
||||
`BizTalk ...`-Namen ist die Änderung beabsichtigt: Checkbox aktivieren und den
|
||||
Servicebestand danach per Discovery abgleichen. Sind in Checkmk bereits die
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 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
|
||||
|
||||
Die am 10.08.2026 bereitgestellten ACC-Artefakte zeigen zwei getrennte
|
||||
@@ -139,7 +144,7 @@ Endpoint-/Namensfix bewusst nicht grüngefärbt.
|
||||
|
||||
## ACC-Abnahme nach Installation
|
||||
|
||||
1. Setup 2.2.4 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
|
||||
stabilen Namen die Rename-/Discovery-Checkbox bewusst aktivieren.
|
||||
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-
|
||||
`mssql`-URIs, schemafreie `host:port`-Angaben, FTP-/SFTP-Ziele mit optionalen
|
||||
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
|
||||
Pfade, SMTP-Empfänger, Named Pipes und relative Receive-Listener) als
|
||||
erwartbar ausgenommen und nicht als Abdeckungsfehler.
|
||||
@@ -197,8 +201,8 @@ Status:
|
||||
|
||||
| Status | Bedingung |
|
||||
| --- | --- |
|
||||
| `OK` | Katalog und Runtimezustand vollständig; alle aktiven prüfbaren Ziele erreichbar. |
|
||||
| `CRIT` | Mindestens ein getestetes TCP-/UDP-Ziel nicht 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 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. |
|
||||
| `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_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_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_inactive_skipped` | Konfigurierte Einträge, deren BizTalk-Artefakt aktuell nicht aktiv ist. |
|
||||
| `biztalk_endpoint_probe_duration_ms` | Gesamtlaufzeit der parallelisierten Socket-Probes in Millisekunden. |
|
||||
|
||||
+21
-1
@@ -21,12 +21,20 @@ 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
|
||||
BizTalk-Runtimezustand gefiltert:
|
||||
|
||||
- Receive Location nur bei `IsDisabled=false`
|
||||
- Send Port nur bei `Status=Started`
|
||||
- dynamischer Send Port nie als statisches Ziel
|
||||
- Scheduling-Komponente nie als Netzwerkziel
|
||||
|
||||
Damit wird ein inzwischen gestopptes/deaktiviertes Artefakt sofort nicht mehr
|
||||
geprüft. Prüffähige aktuelle WMI-Adressen werden bei jedem Minutenlauf direkt
|
||||
@@ -64,7 +72,7 @@ werden nur:
|
||||
- Adaptername
|
||||
- `TCP` oder `UDP`
|
||||
- Host und Port
|
||||
- `enabled` und `autoDiscovered`
|
||||
- `enabled`, `autoDiscovered` und die Best-Effort-Kennzeichnung
|
||||
|
||||
URI-Benutzerinformationen, Kennwörter, Pfade und Querystrings werden nicht
|
||||
geschrieben. Der Katalog liegt im bereits ACL-geschützten `data`-Verzeichnis;
|
||||
@@ -128,6 +136,18 @@ passende nebenwirkungsfreie Netzwerktest. Er bestätigt DNS, Routing,
|
||||
Firewall und einen annehmenden TCP-Port, aber nicht die fachliche Antwort der
|
||||
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,
|
||||
Route und erfolgreichen lokalen Datagrammversand. Für einen belastbaren
|
||||
UDP-Dienstcheck wäre ein adapter-/protokollspezifisches Request/Response-
|
||||
|
||||
@@ -61,15 +61,18 @@ Artefakte. Nur die Textliste ist begrenzt.
|
||||
Gesund, ohne Auflistung aller Ziele:
|
||||
|
||||
```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:
|
||||
|
||||
```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
|
||||
|
||||
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.
|
||||
+17
-2
@@ -53,10 +53,14 @@ Verbindlicher Agent-Dump:
|
||||
|
||||
## Service Discovery
|
||||
|
||||
Das Setup 2.2.4 schließt erst erfolgreich ab, nachdem der erste Providerlauf
|
||||
Das Setup ab 2.2.4 schließt erst erfolgreich ab, nachdem der erste Providerlauf
|
||||
unter dem echten Collector-Konto einen frischen Snapshot und – bei aktivierter
|
||||
Endpoint-Prüfung – einen frisch synchronisierten Katalog erzeugt hat. Die neun
|
||||
stabilen Services müssen vollständig und ohne `UNKNOWN` vorliegen. Diese lokale
|
||||
stabilen Services müssen vollständig 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
|
||||
@@ -64,6 +68,14 @@ 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.
|
||||
2. `LastTaskResult=0`, frischen Snapshot und Log prüfen.
|
||||
3. Agent-Dump im `LocalSystem`-Kontext prüfen.
|
||||
@@ -120,6 +132,9 @@ Soll:
|
||||
- Receive Locations und Send Ports zeigen getrennte expected/unexpected Werte.
|
||||
- Endpoint Reachability zeigt bei Erfolg nur die Gesamtzahl und bei Fehlern
|
||||
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.
|
||||
|
||||
## 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.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
p.paragraph_format.space_after = Pt(0)
|
||||
format_run(p.add_run("Version 2.2.4 | Stand 10.08.2026 | Seite "), size=7.5, color=MUTED)
|
||||
format_run(p.add_run("Version 2.2.6 | Stand 11.08.2026 | Seite "), size=7.5, color=MUTED)
|
||||
add_page_field(p)
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ def build_document():
|
||||
core.title = "BizTalk Checkmk Pulse – Architektur- und Lösungsüberblick"
|
||||
core.subject = "Gesamtarchitektur, Funktionsumfang und Datenaustausch"
|
||||
core.author = "BEW"
|
||||
core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.4"
|
||||
core.comments = "Externe Lösungsübersicht zum implementierten Stand 2.2.6"
|
||||
|
||||
# Cover
|
||||
banner = document.add_table(rows=1, cols=1)
|
||||
@@ -377,9 +377,9 @@ def build_document():
|
||||
document,
|
||||
("Dokumentmerkmal", "Angabe"),
|
||||
(
|
||||
("Lösungsstand", "BizTalk Checkmk Pulse 2.2.4"),
|
||||
("Lösungsstand", "BizTalk Checkmk Pulse 2.2.6"),
|
||||
("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"),
|
||||
),
|
||||
(4.2, 13.0),
|
||||
@@ -543,8 +543,9 @@ def build_document():
|
||||
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, "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, "Nach der Umschaltung erzwingt das Setup einen frischen Katalogabgleich und akzeptiert die Installation erst nach einem erfolgreichen, identitäts- und integritätsgeprüften Providerlauf ohne UNKNOWN in den neun stabilen Services.")
|
||||
add_bullet(document, "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_table(
|
||||
@@ -573,7 +574,7 @@ def build_document():
|
||||
add_callout(document, "Kernaussage: ", "Ein privilegierter Read-only-Collector sammelt, ein unprivilegierter Consumer validiert und übergibt – Checkmk erhält neun stabile Services statt direkten Zugriff auf BizTalk oder SQL.", fill=LIGHT_GREEN, border=GREEN)
|
||||
add_rich_paragraph(
|
||||
document,
|
||||
(("Dokumentbasis: Implementierung und Projektdokumentation von BizTalk Checkmk Pulse 2.2.4 sowie Checkmk User Guide „Local checks“, abgerufen am 10. August 2026.", {"size": 7.8, "color": MUTED}),),
|
||||
(("Dokumentbasis: Implementierung und Projektdokumentation von BizTalk Checkmk Pulse 2.2.6 sowie Checkmk User Guide „Local checks“, abgerufen am 10. August 2026.", {"size": 7.8, "color": MUTED}),),
|
||||
before=8,
|
||||
after=0,
|
||||
)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
<Compile Include="InstallerEngine.cs" />
|
||||
<Compile Include="MainForm.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="SetupOperationLog.cs" />
|
||||
<Compile Include="TaskSchedulerService.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
@@ -37,26 +38,32 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
bool allowServiceNameChange,
|
||||
Action<string> report)
|
||||
{
|
||||
Validate(account, password, isGmsa, environmentName);
|
||||
report = report ?? delegate { };
|
||||
var accountSid = (SecurityIdentifier)new NTAccount(account).Translate(typeof(SecurityIdentifier));
|
||||
report("Konto aufgeloest: " + account + " (" + accountSid.Value + ")");
|
||||
var operationStartedUtc = DateTime.UtcNow;
|
||||
var uiReport = report ?? delegate { };
|
||||
var setupLog = SetupOperationLog.Create(runtimeDirectory, "install-or-update");
|
||||
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 sourceExe = Path.Combine(sourceApplication, "BizTalkCheckmkPulse.exe");
|
||||
var sourceConfig = sourceExe + ".config";
|
||||
var sourceWrapper = Path.Combine(packageDirectory, "biztalk_checkmk_pulse.cmd");
|
||||
RequireFile(sourceExe);
|
||||
RequireFile(sourceConfig);
|
||||
RequireFile(sourceWrapper);
|
||||
var targetExe = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe");
|
||||
var targetConfig = targetExe + ".config";
|
||||
var stagingDirectory = installDirectory + ".staging." + Guid.NewGuid().ToString("N");
|
||||
var backupDirectory = installDirectory + ".backup." + Guid.NewGuid().ToString("N");
|
||||
var targetWrapper = Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd");
|
||||
var previousWrapper = File.Exists(targetWrapper) ? File.ReadAllBytes(targetWrapper) : null;
|
||||
var hadExistingInstallation = Directory.Exists(installDirectory);
|
||||
var hadExistingExecutable = File.Exists(targetExe);
|
||||
byte[] previousWrapper = null;
|
||||
var hadExistingInstallation = false;
|
||||
var hadExistingExecutable = false;
|
||||
var mutationStarted = false;
|
||||
var backupCreated = false;
|
||||
var filesActivated = false;
|
||||
@@ -64,14 +71,29 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
|
||||
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.");
|
||||
if (!isGmsa)
|
||||
{
|
||||
phase = "Collector-Anmeldung pruefen";
|
||||
ValidateBatchLogon(account, password);
|
||||
report("Collector-Anmeldung und 'Log on as a batch job' vor dem Update bestaetigt.");
|
||||
}
|
||||
|
||||
phase = "Update-Staging vorbereiten";
|
||||
report("Phase: " + phase + ".");
|
||||
Directory.CreateDirectory(stagingDirectory);
|
||||
var stagedExe = Path.Combine(stagingDirectory, "BizTalkCheckmkPulse.exe");
|
||||
var stagedConfig = stagedExe + ".config";
|
||||
@@ -81,38 +103,45 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
stagedConfig,
|
||||
File.Exists(targetConfig) ? targetConfig : null,
|
||||
environmentName);
|
||||
var stagedServiceNames = RunSelfTest(stagedExe);
|
||||
var stagedServiceNames = RunSelfTest(stagedExe, 9, "Staging", setupLog);
|
||||
report("Update-Staging validiert. Umgebung=" + (effectiveEnvironment.Length == 0 ? "(keine)" : effectiveEnvironment) + ".");
|
||||
|
||||
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(
|
||||
installedServiceNames,
|
||||
stagedServiceNames,
|
||||
allowServiceNameChange);
|
||||
report(serviceNameChange.Length == 0
|
||||
? "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.
|
||||
phase = "Vorhandenen Scheduled Task anhalten";
|
||||
report("Phase: " + phase + ".");
|
||||
mutationStarted = true;
|
||||
scheduler.DeleteIfExists(TaskName);
|
||||
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
|
||||
|
||||
if (hadExistingInstallation)
|
||||
{
|
||||
phase = "Vorhandene Programmversion sichern";
|
||||
Directory.Move(installDirectory, backupDirectory);
|
||||
backupCreated = true;
|
||||
}
|
||||
|
||||
phase = "Neue Programmversion aktivieren";
|
||||
Directory.Move(stagingDirectory, installDirectory);
|
||||
filesActivated = true;
|
||||
report(hadExistingInstallation
|
||||
? "Programmdateien atomar auf die neue Version umgestellt."
|
||||
: "Programmdateien installiert: " + installDirectory);
|
||||
|
||||
phase = "Runtime-Verzeichnisse und ACLs einrichten";
|
||||
var dataDirectory = Path.Combine(runtimeDirectory, "data");
|
||||
var logDirectory = Path.Combine(runtimeDirectory, "logs");
|
||||
Directory.CreateDirectory(runtimeDirectory);
|
||||
@@ -125,13 +154,16 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
ApplyDirectoryAcl(logDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.Modify);
|
||||
report("Least-Privilege-Verzeichnisrechte gesetzt; vorhandene Runtime-Daten bleiben erhalten.");
|
||||
|
||||
phase = "Checkmk Local Check installieren";
|
||||
Directory.CreateDirectory(checkmkLocalDirectory);
|
||||
File.Copy(sourceWrapper, targetWrapper, true);
|
||||
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.");
|
||||
|
||||
phase = "Provider-Abnahmelauf registrieren und starten";
|
||||
var runtimeValidationStartedUtc = DateTime.UtcNow;
|
||||
scheduler.RegisterValidationAndStart(
|
||||
TaskName,
|
||||
@@ -142,6 +174,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
isGmsa);
|
||||
report("Einmaliger Provider-Abnahmelauf mit erzwungenem Endpoint-Katalogabgleich gestartet.");
|
||||
|
||||
phase = "Provider-Abnahmelauf abwarten";
|
||||
var completedRun = scheduler.WaitForSuccessfulRun(
|
||||
TaskName,
|
||||
runtimeValidationStartedUtc,
|
||||
@@ -149,12 +182,14 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
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,
|
||||
@@ -165,19 +200,28 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
report("Validierter minuetlicher Scheduled Task registriert: " + TaskName);
|
||||
|
||||
TryDeleteDirectory(backupDirectory, report);
|
||||
report("Installation/Update erfolgreich abgeschlossen.");
|
||||
}
|
||||
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>();
|
||||
if (mutationStarted)
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler.DeleteIfExists(TaskName);
|
||||
report("Rollback: Scheduled Task gestoppt und entfernt.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Task stoppen: " + ex.Message);
|
||||
setupLog.WriteException("Rollback: Task stoppen", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +236,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Programmdateien wiederherstellen: " + ex.Message);
|
||||
setupLog.WriteException("Rollback: Programmdateien wiederherstellen", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,10 +245,12 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
try
|
||||
{
|
||||
RestoreWrapper(targetWrapper, previousWrapper);
|
||||
report("Rollback: Checkmk-Wrapper auf den vorherigen Stand zurueckgesetzt.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
|
||||
setupLog.WriteException("Rollback: Checkmk-Wrapper wiederherstellen", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,19 +276,28 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Scheduled Task wiederherstellen: " + ex.Message);
|
||||
setupLog.WriteException("Rollback: Scheduled Task wiederherstellen", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var rollback = rollbackFailures.Count == 0
|
||||
? "Rollback erfolgreich."
|
||||
: "Rollback unvollstaendig: " + string.Join(" | ", rollbackFailures);
|
||||
var rollback = !mutationStarted
|
||||
? "Keine Systemaenderung vorgenommen; kein Rollback erforderlich."
|
||||
: rollbackFailures.Count == 0
|
||||
? "Rollback erfolgreich."
|
||||
: "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(
|
||||
"Installation/Update fehlgeschlagen. " + rollback + " Ursache: " + installException.Message,
|
||||
"Installation/Update fehlgeschlagen in Phase '" + phase + "'. "
|
||||
+ rollback + " Ursache: " + installException.Message + " " + logReference,
|
||||
installException);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryDeleteDirectory(stagingDirectory, null);
|
||||
setupLog.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,8 +508,20 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
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")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
@@ -464,24 +532,74 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
using (var process = Process.Start(start))
|
||||
{
|
||||
if (process == null) throw new InvalidOperationException("Self-Test konnte nicht gestartet werden.");
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
var error = process.StandardError.ReadToEnd();
|
||||
var outputRead = process.StandardOutput.ReadToEndAsync();
|
||||
var errorRead = process.StandardError.ReadToEndAsync();
|
||||
if (!process.WaitForExit(30000))
|
||||
{
|
||||
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.");
|
||||
}
|
||||
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();
|
||||
if (serviceNames.Distinct(StringComparer.Ordinal).Count() != serviceNames.Length)
|
||||
throw new InvalidOperationException("Self-Test fehlgeschlagen: Checkmk-Servicenamen sind nicht eindeutig.");
|
||||
return serviceNames;
|
||||
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 serviceNames = lines.Select(ExtractServiceName).ToArray();
|
||||
if (serviceNames.Distinct(StringComparer.Ordinal).Count() != serviceNames.Length)
|
||||
throw new InvalidOperationException(
|
||||
"Self-Test '" + label + "' fehlgeschlagen: Checkmk-Servicenamen sind nicht eindeutig. "
|
||||
+ "Services=[" + string.Join(", ", 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,
|
||||
@@ -501,13 +619,21 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
using (var process = Process.Start(start))
|
||||
{
|
||||
if (process == null) throw new InvalidOperationException("Runtime-Abnahme konnte nicht gestartet werden.");
|
||||
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||
var error = process.StandardError.ReadToEnd().Trim();
|
||||
var outputRead = process.StandardOutput.ReadToEndAsync();
|
||||
var errorRead = process.StandardError.ReadToEndAsync();
|
||||
if (!process.WaitForExit(30000))
|
||||
{
|
||||
process.Kill();
|
||||
throw new TimeoutException("Runtime-Abnahme hat das Zeitlimit ueberschritten.");
|
||||
process.WaitForExit();
|
||||
System.Threading.Tasks.Task.WaitAll(outputRead, errorRead);
|
||||
throw new TimeoutException(
|
||||
"Runtime-Abnahme hat das Zeitlimit von 30 Sekunden ueberschritten. Stdout="
|
||||
+ CompactProcessText(outputRead.Result)
|
||||
+ ", Stderr=" + CompactProcessText(errorRead.Result) + ".");
|
||||
}
|
||||
System.Threading.Tasks.Task.WaitAll(outputRead, errorRead);
|
||||
var output = outputRead.Result.Trim();
|
||||
var error = errorRead.Result.Trim();
|
||||
|
||||
if (process.ExitCode != 0
|
||||
|| !output.StartsWith("RUNTIME_VALIDATION_V1 ", StringComparison.Ordinal))
|
||||
@@ -542,7 +668,10 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
var removed = installed.Except(staged, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||
var added = staged.Except(installed, StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
||||
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(
|
||||
"Sicherheitsstopp: Das Update wuerde Checkmk-Servicenamen aendern. "
|
||||
@@ -553,5 +682,50 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
|
||||
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: AssemblyCompany("BEW")]
|
||||
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
||||
[assembly: AssemblyVersion("2.2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.4.0")]
|
||||
[assembly: AssemblyVersion("2.2.7.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.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
@@ -11,6 +13,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
private const int TaskTriggerTime = 1;
|
||||
private const int TaskActionExecute = 0;
|
||||
private const int TaskInstancesIgnoreNew = 2;
|
||||
private const int TaskStateReady = 3;
|
||||
private const int TaskStateRunning = 4;
|
||||
|
||||
public void RegisterValidationAndStart(
|
||||
@@ -165,11 +168,20 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
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=" + lastState
|
||||
+ ", last_result=" + lastResult
|
||||
"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);
|
||||
}
|
||||
@@ -179,8 +191,8 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
|
||||
throw new TimeoutException(
|
||||
"Providerlauf wurde innerhalb von " + Math.Ceiling(timeout.TotalSeconds)
|
||||
+ " Sekunden nicht erfolgreich abgeschlossen. state=" + lastState
|
||||
+ ", last_result=" + lastResult
|
||||
+ " Sekunden nicht erfolgreich abgeschlossen. state=" + DescribeState(lastState)
|
||||
+ ", last_result=" + DescribeResult(lastResult)
|
||||
+ ", last_run=" + (lastRunUtc.HasValue ? lastRunUtc.Value.ToString("o") : "(noch keiner)") + ".");
|
||||
}
|
||||
finally
|
||||
@@ -197,13 +209,77 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
DateTime? lastRunUtc,
|
||||
DateTime notBeforeUtc)
|
||||
{
|
||||
return taskState != TaskStateRunning
|
||||
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)
|
||||
{
|
||||
dynamic service = null;
|
||||
|
||||
@@ -357,6 +357,8 @@ namespace BizTalkCheckmkPulse
|
||||
/// <summary>
|
||||
/// Formatiert die Netzwerk-Erreichbarkeit aller aktuell aktiven, pruefbaren BizTalk-Endpunkte.
|
||||
/// </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)
|
||||
{
|
||||
var endpointState = result.EndpointConnectivity;
|
||||
@@ -378,7 +380,12 @@ namespace BizTalkCheckmkPulse
|
||||
"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 incomplete = endpointState.Results.Count != endpointState.Active
|
||||
|| endpointState.UnresolvedActive > 0
|
||||
@@ -391,7 +398,7 @@ namespace BizTalkCheckmkPulse
|
||||
: CheckState.Ok;
|
||||
var metrics = string.Format(
|
||||
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.Active,
|
||||
endpointState.UniqueTargets,
|
||||
@@ -400,15 +407,21 @@ namespace BizTalkCheckmkPulse
|
||||
failed.Length,
|
||||
endpointState.UnresolvedActive,
|
||||
endpointState.ExpectedNonProbeableActive,
|
||||
endpointState.BestEffortActive,
|
||||
ignoredBestEffort.Length,
|
||||
endpointState.ManualOverridesActive,
|
||||
endpointState.SkippedInactive,
|
||||
endpointState.ProbeDurationMilliseconds);
|
||||
var detail = new StringBuilder();
|
||||
if (failed.Length == 0 && !incomplete)
|
||||
{
|
||||
detail.Append("Alle ").Append(endpointState.Active).Append(" aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar")
|
||||
.Append(" (unique_targets=").Append(endpointState.UniqueTargets)
|
||||
detail.Append("Alle regulaer alarmierenden aktiven Send-/Receive-Endpunkte sind erreichbar")
|
||||
.Append(" (active=").Append(endpointState.Active)
|
||||
.Append(", available=").Append(available)
|
||||
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
|
||||
.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(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds).Append(")");
|
||||
}
|
||||
@@ -420,6 +433,8 @@ namespace BizTalkCheckmkPulse
|
||||
.Append(", failed=").Append(failed.Length)
|
||||
.Append(", unresolved=").Append(endpointState.UnresolvedActive)
|
||||
.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(", unique_targets=").Append(endpointState.UniqueTargets)
|
||||
.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})(?:[/\\].*)?$",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex DatabaseProviderPattern = new Regex(
|
||||
@"/(?:Oracle|SQLServer)(?:[/;?]|$)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag.
|
||||
/// </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(
|
||||
EndpointCandidate candidate,
|
||||
out EndpointCatalogEntry entry,
|
||||
@@ -30,6 +38,8 @@ namespace BizTalkCheckmkPulse
|
||||
/// <summary>
|
||||
/// Trennt sicher pruefbare Ziele von erwartbar lokalen/adapterinternen Adressen und echten Aufloesungsluecken.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Zu klassifizierender BizTalk-Transport.</param>
|
||||
/// <returns>Klassifizierung sowie gegebenenfalls das pruefbare Socket-Ziel.</returns>
|
||||
public static EndpointResolution Analyze(EndpointCandidate candidate)
|
||||
{
|
||||
if (candidate == null)
|
||||
@@ -42,6 +52,13 @@ namespace BizTalkCheckmkPulse
|
||||
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();
|
||||
if (address.Length == 0)
|
||||
{
|
||||
@@ -80,6 +97,33 @@ namespace BizTalkCheckmkPulse
|
||||
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;
|
||||
string protocol;
|
||||
string host;
|
||||
@@ -115,8 +159,306 @@ namespace BizTalkCheckmkPulse
|
||||
Host = host,
|
||||
Port = port,
|
||||
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
|
||||
{
|
||||
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(
|
||||
string address,
|
||||
string adapterName,
|
||||
|
||||
@@ -20,6 +20,12 @@ namespace BizTalkCheckmkPulse
|
||||
private readonly int _maxBytes;
|
||||
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)
|
||||
{
|
||||
_path = path;
|
||||
@@ -27,6 +33,11 @@ namespace BizTalkCheckmkPulse
|
||||
_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)
|
||||
{
|
||||
var info = new FileInfo(_path);
|
||||
@@ -112,7 +123,9 @@ namespace BizTalkCheckmkPulse
|
||||
Host = ReadAttribute(node, "host"),
|
||||
Port = ReadInt(node, "port", 1, 65535),
|
||||
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);
|
||||
catalog.Entries.Add(entry);
|
||||
@@ -126,6 +139,10 @@ namespace BizTalkCheckmkPulse
|
||||
return catalog;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validiert und ersetzt den Endpoint-Katalog atomar.
|
||||
/// </summary>
|
||||
/// <param name="catalog">Zu persistierender geheimnisfreier Katalog.</param>
|
||||
public void Write(EndpointCatalog catalog)
|
||||
{
|
||||
if (catalog == null)
|
||||
@@ -170,7 +187,8 @@ namespace BizTalkCheckmkPulse
|
||||
new XAttribute("host", entry.Host),
|
||||
new XAttribute("port", entry.Port),
|
||||
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);
|
||||
@@ -284,5 +302,29 @@ namespace BizTalkCheckmkPulse
|
||||
|
||||
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 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)
|
||||
{
|
||||
_options = options;
|
||||
_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)
|
||||
{
|
||||
var state = result.EndpointConnectivity;
|
||||
@@ -141,7 +150,13 @@ namespace BizTalkCheckmkPulse
|
||||
foreach (var probeResult in probeResults)
|
||||
{
|
||||
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(
|
||||
"Endpoint unavailable. endpoint=" + Display(probeResult.Endpoint)
|
||||
@@ -157,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(
|
||||
EndpointCatalog existing,
|
||||
IEnumerable<EndpointCandidate> candidates,
|
||||
@@ -223,6 +245,10 @@ namespace BizTalkCheckmkPulse
|
||||
/// 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.
|
||||
/// </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(
|
||||
EndpointCatalog catalog,
|
||||
IEnumerable<EndpointCandidate> candidates,
|
||||
@@ -284,10 +310,12 @@ namespace BizTalkCheckmkPulse
|
||||
x.Enabled
|
||||
&& !string.Equals(x.ArtifactType, "Manual", StringComparison.OrdinalIgnoreCase)
|
||||
&& !activeKeys.Contains(x.Key));
|
||||
return selected
|
||||
var resolved = selected
|
||||
.GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => x.First())
|
||||
.ToArray();
|
||||
state.BestEffortActive = resolved.Count(x => x.BestEffort);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -356,6 +384,13 @@ namespace BizTalkCheckmkPulse
|
||||
}).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)
|
||||
{
|
||||
if (targetCount <= 0)
|
||||
@@ -465,6 +500,11 @@ namespace BizTalkCheckmkPulse
|
||||
: "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)
|
||||
{
|
||||
var artifact = string.IsNullOrWhiteSpace(endpoint.ApplicationName)
|
||||
|
||||
@@ -305,7 +305,20 @@ namespace BizTalkCheckmkPulse
|
||||
public int Port { get; set; }
|
||||
public bool Enabled { 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)
|
||||
{
|
||||
return NormalizeKeyPart(artifactType)
|
||||
@@ -371,6 +384,14 @@ namespace BizTalkCheckmkPulse
|
||||
public int SkippedInactive { get; set; }
|
||||
public int UnresolvedActive { 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 UniqueTargets { get; set; }
|
||||
public long ProbeDurationMilliseconds { get; set; }
|
||||
|
||||
@@ -115,7 +115,9 @@ namespace BizTalkCheckmkPulse
|
||||
+ " endpoints_active="
|
||||
+ result.EndpointConnectivity.Active
|
||||
+ " 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="
|
||||
+ result.EndpointConnectivity.UnresolvedActive
|
||||
+ " endpoints_expected_non_socket="
|
||||
@@ -164,7 +166,14 @@ namespace BizTalkCheckmkPulse
|
||||
+ " catalogUtc=" + (validation.CatalogSynchronizedUtc.HasValue
|
||||
? validation.CatalogSynchronizedUtc.Value.ToString("o")
|
||||
: "disabled")
|
||||
+ " stableServices=" + validation.StableServiceCount);
|
||||
+ " 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@ using System.Reflection;
|
||||
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
|
||||
[assembly: AssemblyCompany("BEW")]
|
||||
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
||||
[assembly: AssemblyVersion("2.2.4.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.4.0")]
|
||||
[assembly: AssemblyVersion("2.2.7.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.7.0")]
|
||||
|
||||
@@ -43,12 +43,34 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
|
||||
string serviceError;
|
||||
string[] unknownServiceLines;
|
||||
var expectedServices = formatter.FormatSelfTest().Select(ExtractServiceName).ToArray();
|
||||
if (!ContainsExactStableContract(snapshot.Lines, expectedServices, out serviceError))
|
||||
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)
|
||||
{
|
||||
@@ -85,14 +107,17 @@ namespace BizTalkCheckmkPulse
|
||||
return RuntimeValidationResult.Success(
|
||||
snapshot.GeneratedUtc,
|
||||
catalogUtc,
|
||||
expectedServices.Length);
|
||||
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
|
||||
{
|
||||
@@ -123,18 +148,11 @@ namespace BizTalkCheckmkPulse
|
||||
return false;
|
||||
}
|
||||
|
||||
var unknown = (lines ?? Enumerable.Empty<string>())
|
||||
unknownServiceLines = (lines ?? Enumerable.Empty<string>())
|
||||
.Where(x => x != null && x.StartsWith("3 ", StringComparison.Ordinal))
|
||||
.Select(ExtractServiceName)
|
||||
.Where(x => expected.Contains(x, StringComparer.Ordinal))
|
||||
.OrderBy(x => x, StringComparer.Ordinal)
|
||||
.Where(x => expected.Contains(ExtractServiceName(x), StringComparer.Ordinal))
|
||||
.OrderBy(ExtractServiceName, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (unknown.Length != 0)
|
||||
{
|
||||
error = "Snapshot enthaelt unzuverlaessige UNKNOWN-Zustaende in stabilen Services. UNKNOWN=["
|
||||
+ string.Join(", ", unknown) + "].";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
@@ -159,21 +177,31 @@ namespace BizTalkCheckmkPulse
|
||||
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)
|
||||
public static RuntimeValidationResult Success(
|
||||
DateTime generatedUtc,
|
||||
DateTime? catalogUtc,
|
||||
int serviceCount,
|
||||
IEnumerable<string> acceptedUnknownLines)
|
||||
{
|
||||
return new RuntimeValidationResult
|
||||
{
|
||||
IsSuccess = true,
|
||||
GeneratedUtc = generatedUtc,
|
||||
CatalogSynchronizedUtc = catalogUtc,
|
||||
StableServiceCount = serviceCount
|
||||
StableServiceCount = serviceCount,
|
||||
AcceptedUnknownLines = (acceptedUnknownLines ?? Enumerable.Empty<string>()).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public static RuntimeValidationResult Failed(string error)
|
||||
{
|
||||
return new RuntimeValidationResult { Error = error };
|
||||
return new RuntimeValidationResult
|
||||
{
|
||||
Error = error,
|
||||
AcceptedUnknownLines = new string[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,13 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded);
|
||||
Run("EndpointAddressesResolveWithoutSecrets", EndpointAddressesResolveWithoutSecrets);
|
||||
Run("AdapterSpecificEndpointAddressesResolve", AdapterSpecificEndpointAddressesResolve);
|
||||
Run("SchedulerDatabaseAndSapEndpointsAreClassifiedSafely", SchedulerDatabaseAndSapEndpointsAreClassifiedSafely);
|
||||
Run("EndpointClassificationSeparatesExpectedAndUnresolved", EndpointClassificationSeparatesExpectedAndUnresolved);
|
||||
Run("EndpointCatalogPreservesManualOverrides", EndpointCatalogPreservesManualOverrides);
|
||||
Run("EndpointRuntimeUsesCurrentAddressAndManualOverrides", EndpointRuntimeUsesCurrentAddressAndManualOverrides);
|
||||
Run("EndpointCatalogRoundTrip", EndpointCatalogRoundTrip);
|
||||
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
|
||||
Run("BestEffortEndpointFailuresRemainNonAlerting", BestEffortEndpointFailuresRemainNonAlerting);
|
||||
Run("EndpointOutputExplainsOnlyRealResolutionGaps", EndpointOutputExplainsOnlyRealResolutionGaps);
|
||||
Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval);
|
||||
Run("ForcedEndpointRefreshRewritesFreshCatalog", ForcedEndpointRefreshRewritesFreshCatalog);
|
||||
@@ -42,6 +44,9 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
Run("DefaultServiceContractIsExact", DefaultServiceContractIsExact);
|
||||
Run("InstallerRejectsUnconfirmedServiceRename", InstallerRejectsUnconfirmedServiceRename);
|
||||
Run("InstallerAllowsConfirmedServiceRename", InstallerAllowsConfirmedServiceRename);
|
||||
Run("InstallerAcceptsLegacyEightServiceSelfTest", InstallerAcceptsLegacyEightServiceSelfTest);
|
||||
Run("InstallerRequiresNineServicesForCandidate", InstallerRequiresNineServicesForCandidate);
|
||||
Run("InstallerAllowsAdditiveServiceContract", InstallerAllowsAdditiveServiceContract);
|
||||
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
|
||||
Run("RuntimeValidationAcceptsFreshConsistentArtifacts", RuntimeValidationAcceptsFreshConsistentArtifacts);
|
||||
Run("RuntimeValidationRejectsPreInstallSnapshot", RuntimeValidationRejectsPreInstallSnapshot);
|
||||
@@ -49,10 +54,13 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
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("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
||||
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
||||
@@ -464,6 +472,73 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
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()
|
||||
{
|
||||
var candidate = new EndpointCandidate
|
||||
@@ -611,6 +686,33 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
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()
|
||||
{
|
||||
var result = new ProbeResult();
|
||||
@@ -646,7 +748,9 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
ActiveCandidates = 150,
|
||||
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);
|
||||
store.Write(catalog);
|
||||
var loaded = store.Read("ACC");
|
||||
@@ -654,8 +758,18 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
AssertEqual(1, loaded.Entries.Count, "catalog entry count");
|
||||
AssertEqual("api.example.test", loaded.Entries[0].Host, "catalog host");
|
||||
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");
|
||||
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
|
||||
{
|
||||
@@ -810,6 +924,51 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
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)
|
||||
{
|
||||
var start = line.IndexOf('"') + 1;
|
||||
@@ -979,8 +1138,55 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
"BEW\\t231bizmon",
|
||||
true,
|
||||
lines);
|
||||
Assert(!result.IsSuccess, "UNKNOWN stable service must fail runtime acceptance");
|
||||
Assert(result.Error.Contains("UNKNOWN=[BizTalk Platform]"), "UNKNOWN stable service reason missing");
|
||||
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()
|
||||
@@ -1030,6 +1236,19 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user