Compare commits

..
1 Commits
Author SHA1 Message Date
admin 0f8e8034e6 Harden RTM endpoint adapter handling 2026-08-11 13:56:30 +02:00
18 changed files with 759 additions and 34 deletions
+10
View File
@@ -446,6 +446,16 @@ zurückgerollt. UNKNOWN in Platform, SQL Access, Suspensions, Host Instances,
Receive Locations, Send Ports, Orchestrations oder Event Log bleibt Receive Locations, Send Ports, Orchestrations oder Event Log bleibt
blockierend, weil dort die Kern-Datenerhebung nicht zuverlässig belegt ist. blockierend, weil dort die Kern-Datenerhebung nicht zuverlässig belegt ist.
Version 2.2.7 klassifiziert die im RTM-Abnahmelauf identifizierten Adapter
präziser. Scheduling-Komponenten sind keine Netzwerkziele und werden komplett
ausgenommen. `Database://host[:port]/.../Oracle` wird per TCP auf den
expliziten Port oder den Oracle-Standard 1521 geprüft. WCF-SAP verwendet den
ersten SAProuter-Hop oder das Paar `ListenerGwHost`/`ListenerGwServ`; dabei
entspricht `sapgwNN` dem TCP-Port `33NN`. Diese beiden Adapterklassen laufen
als Best-Effort-Diagnose: Ein Fehlschlag bleibt über Metriken sichtbar, erzeugt
aber keinen Alarm. Alle regulären und manuellen Ziele behalten ihre bisherige
strenge Statuslogik.
Der installierte Task selbst läuft mit `RunLevel Limited`. Der Installer Der installierte Task selbst läuft mit `RunLevel Limited`. Der Installer
übergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle; übergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle;
es wird weder in einer Prozesskommandozeile noch in Config oder Log es wird weder in einer Prozesskommandozeile noch in Config oder Log
+21 -6
View File
@@ -26,6 +26,15 @@ Checkmk-Service `BizTalk Endpoint Reachability` zusammengefasst:
- HTTP(S), SFTP, FTP, WCF/`net.tcp` und UNC/SMB werden durch einen reinen - HTTP(S), SFTP, FTP, WCF/`net.tcp` und UNC/SMB werden durch einen reinen
TCP-Verbindungsaufbau zu Host und Port geprüft. Explizite TCP-Verbindungsaufbau zu Host und Port geprüft. Explizite
`udp://host:port`-Ziele verwenden ein UDP-Datagramm. `udp://host:port`-Ziele verwenden ein UDP-Datagramm.
- Scheduling-Komponenten werden vollständig aus der Endpoint-Prüfung
ausgeschlossen. Oracle-`DATABASE`-Ziele werden aus
`Database://host[:port]/.../Oracle` auf TCP 1521 beziehungsweise den
expliziten Port reduziert. WCF-SAP nutzt den ersten SAProuter-Hop oder
`ListenerGwHost`/`ListenerGwServ`; `sapgwNN` wird als TCP `33NN` aufgelöst.
- Automatisch abgeleitete DATABASE-/WCF-SAP-Probes sind Best-Effort:
erfolgreiche Tests bleiben messbar, nicht ableitbare oder nicht erreichbare
Ziele erzeugen jedoch weder `UNKNOWN` noch `WARN`/`CRIT`. Für verbindliche
Alarmierung kann weiterhin ein manueller Katalogeintrag verwendet werden.
- Es werden weder HTTP-Aufrufe noch Anmeldungen, fachliche Requests oder - Es werden weder HTTP-Aufrufe noch Anmeldungen, fachliche Requests oder
BizTalk-Nachrichten gesendet. Der Check validiert DNS, Netzwerkpfad, BizTalk-Nachrichten gesendet. Der Check validiert DNS, Netzwerkpfad,
Firewall und bei TCP einen annehmenden Zielport, nicht die fachliche Firewall und bei TCP einen annehmenden Zielport, nicht die fachliche
@@ -128,11 +137,13 @@ Auch schemafreie `host:port`-Angaben sowie FTP-/SFTP-Adressen der Form
nicht in den Endpoint-Katalog übernommen. nicht in den Endpoint-Katalog übernommen.
Nicht jede aktive BizTalk-Adresse ist selbst ein Socket-Ziel. Dynamische Send Nicht jede aktive BizTalk-Adresse ist selbst ein Socket-Ziel. Dynamische Send
Ports, lokale Pfade, SMTP-Empfänger, Named Pipes und relative HTTP-/WCF- Ports, lokale Pfade, SMTP-Empfänger, Named Pipes, relative HTTP-/WCF-
Receive-Listener werden deshalb als erwartbar nicht socket-prüfbar behandelt Receive-Listener, Scheduling-Komponenten sowie nicht sicher ableitbare
und erzeugen kein `UNKNOWN`. Nur eine aktive statische Adresse, die tatsächlich DATABASE-/WCF-SAP-Adressen werden deshalb als erwartbar nicht socket-prüfbar
ein Netzwerkziel enthalten müsste und nicht sicher aufgelöst werden kann, behandelt und erzeugen kein `UNKNOWN`. Nur eine andere aktive statische
bleibt `UNKNOWN` und wird geheimnisfrei mit Artefakt, Adapter und Grund genannt. Adresse, die tatsächlich ein Netzwerkziel enthalten müsste und nicht sicher
aufgelöst werden kann, bleibt `UNKNOWN` und wird geheimnisfrei mit Artefakt,
Adapter und Grund genannt.
Der geheimnisfreie Katalog liegt unter Der geheimnisfreie Katalog liegt unter
`%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml`. Fehlt er, wird er beim `%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml`. Fehlt er, wird er beim
@@ -158,6 +169,10 @@ Beispielausgaben: [docs/ExampleOutput.md](docs/ExampleOutput.md)
Endpoint-Katalog, manuelle Overrides und Protokollgrenzen: Endpoint-Katalog, manuelle Overrides und Protokollgrenzen:
[docs/EndpointCatalog.md](docs/EndpointCatalog.md) [docs/EndpointCatalog.md](docs/EndpointCatalog.md)
Die RTM-Adapterregeln und ihre Herstellerquellen sind in
[docs/RTM-Endpoint-Adapterregeln-2026-08-11.md](docs/RTM-Endpoint-Adapterregeln-2026-08-11.md)
festgehalten.
## Voraussetzungen ## Voraussetzungen
Build-Host: Build-Host:
@@ -435,7 +450,7 @@ liest den nächsten atomar publizierten Snapshot.
| Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` prüfen. | | Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` prüfen. |
| Receive Locations / Send Ports sind CRIT | `affected=` prüfen; nur fachlich bewusst inaktive Namen exakt in die jeweilige Allowlist aufnehmen. | | Receive Locations / Send Ports sind CRIT | `affected=` prüfen; nur fachlich bewusst inaktive Namen exakt in die jeweilige Allowlist aufnehmen. |
| Endpoint Reachability ist CRIT | Nur `unavailable=` prüfen; DNS, Zielport, Firewall und externen Dienst kontrollieren. | | Endpoint Reachability ist CRIT | Nur `unavailable=` prüfen; DNS, Zielport, Firewall und externen Dienst kontrollieren. |
| Endpoint Reachability ist UNKNOWN | `unresolved_endpoints=`, WMI-Vollständigkeit, `endpoints.xml` und wöchentlichen Refresh prüfen; indirekte Adapterziele bei Bedarf geheimnisfrei manuell überschreiben. | | Endpoint Reachability ist UNKNOWN | `unresolved_endpoints=`, WMI-Vollständigkeit, `endpoints.xml` und wöchentlichen Refresh prüfen. Schedule sowie nicht ableitbare DATABASE-/WCF-SAP-Ziele sind ab 2.2.7 bereits ausgenommen; andere indirekte Ziele bei Bedarf geheimnisfrei manuell überschreiben. |
| `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema prüfen; keine Rechte ausweiten. | | `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema prüfen; keine Rechte ausweiten. |
| Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos prüfen. | | Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos prüfen. |
| Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL prüfen. | | Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL prüfen. |
+7
View File
@@ -56,6 +56,13 @@ unverändert `UNKNOWN`, bis Auflösungslücken, Katalog-/Probeprobleme oder
fehlende manuelle Overrides betrieblich geklärt sind. `UNKNOWN` in Platform, fehlende manuelle Overrides betrieblich geklärt sind. `UNKNOWN` in Platform,
SQL oder einem anderen Kernservice bleibt ein blockierender Abnahmefehler. 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: Version 2.2.4 nimmt die umgeschaltete Installation zusätzlich vollständig ab:
- einmaliger, triggerloser Providerlauf unter dem angegebenen Collector-Konto, - einmaliger, triggerloser Providerlauf unter dem angegebenen Collector-Konto,
@@ -1,5 +1,10 @@
# ACC-Analyse: Checkmk-Datenalter und Endpoint Reachability # ACC-Analyse: Checkmk-Datenalter und Endpoint Reachability
> Nachtrag 11.08.2026: Version 2.2.7 behebt die aus der RTM-Ausgabe
> identifizierten Schedule-, Oracle-DATABASE- und WCF-SAP-Sonderfälle. Die
> verbindliche Nachfolgeregel steht in
> [RTM-Endpoint-Adapterregeln-2026-08-11.md](RTM-Endpoint-Adapterregeln-2026-08-11.md).
## Befund ## Befund
Die am 10.08.2026 bereitgestellten ACC-Artefakte zeigen zwei getrennte Die am 10.08.2026 bereitgestellten ACC-Artefakte zeigen zwei getrennte
+8 -2
View File
@@ -185,6 +185,10 @@ Zweck:
- Erkennt unter anderem Standard-URIs, UNC, MSMQ, `net.tcp`, WCF-SQL- - Erkennt unter anderem Standard-URIs, UNC, MSMQ, `net.tcp`, WCF-SQL-
`mssql`-URIs, schemafreie `host:port`-Angaben, FTP-/SFTP-Ziele mit optionalen `mssql`-URIs, schemafreie `host:port`-Angaben, FTP-/SFTP-Ziele mit optionalen
Benutzerinformationen sowie lokale HTTP-/WCF-Wildcard-Listener. Benutzerinformationen sowie lokale HTTP-/WCF-Wildcard-Listener.
- Schließt Scheduling-Komponenten vollständig aus. Oracle-`DATABASE`- und
WCF-SAP-Adressen werden, soweit eindeutig möglich, als TCP-Best-Effort-Ziel
geprüft; fehlende Ableitung und fehlgeschlagene Best-Effort-Probes alarmieren
nicht.
- Behandelt Adressen ohne eigenständiges Socket-Ziel (zum Beispiel lokale - Behandelt Adressen ohne eigenständiges Socket-Ziel (zum Beispiel lokale
Pfade, SMTP-Empfänger, Named Pipes und relative Receive-Listener) als Pfade, SMTP-Empfänger, Named Pipes und relative Receive-Listener) als
erwartbar ausgenommen und nicht als Abdeckungsfehler. erwartbar ausgenommen und nicht als Abdeckungsfehler.
@@ -197,8 +201,8 @@ Status:
| Status | Bedingung | | Status | Bedingung |
| --- | --- | | --- | --- |
| `OK` | Katalog und Runtimezustand vollständig; alle aktiven prüfbaren Ziele erreichbar. | | `OK` | Katalog und Runtimezustand vollständig; alle alarmierenden Ziele erreichbar. Best-Effort-Fehler bei DATABASE/WCF-SAP sind höchstens als Metrik sichtbar. |
| `CRIT` | Mindestens ein getestetes TCP-/UDP-Ziel nicht erreichbar. | | `CRIT` | Mindestens ein reguläres oder manuell konfiguriertes TCP-/UDP-Ziel nicht erreichbar. |
| `UNKNOWN` | Katalog fehlt/ist ungültig, Wochenabgleich scheitert, Runtimezustand ist unvollständig oder ein aktiver externer Endpoint kann nicht sicher auf Host/Port reduziert werden. | | `UNKNOWN` | Katalog fehlt/ist ungültig, Wochenabgleich scheitert, Runtimezustand ist unvollständig oder ein aktiver externer Endpoint kann nicht sicher auf Host/Port reduziert werden. |
| `OK` mit Hinweis | Nur wenn `ProbeEndpointConnectivity=false`; der Test ist bewusst deaktiviert. | | `OK` mit Hinweis | Nur wenn `ProbeEndpointConnectivity=false`; der Test ist bewusst deaktiviert. |
@@ -214,6 +218,8 @@ Metriken:
| `biztalk_endpoints_failed` | Nicht erreichbare aktive Artefaktziele; CRIT ab 1. | | `biztalk_endpoints_failed` | Nicht erreichbare aktive Artefaktziele; CRIT ab 1. |
| `biztalk_endpoints_unresolved` | Aktive statische Netzwerkadressen ohne sicher bestimmbaren Host/Port; `UNKNOWN` ab 1, sofern kein manueller Override existiert. | | `biztalk_endpoints_unresolved` | Aktive statische Netzwerkadressen ohne sicher bestimmbaren Host/Port; `UNKNOWN` ab 1, sofern kein manueller Override existiert. |
| `biztalk_endpoints_expected_non_socket` | Aktive Artefakte, deren Adresse erwartbar kein eigenständiges TCP-/UDP-Ziel enthält. | | `biztalk_endpoints_expected_non_socket` | Aktive Artefakte, deren Adresse erwartbar kein eigenständiges TCP-/UDP-Ziel enthält. |
| `biztalk_endpoints_best_effort` | Automatisch abgeleitete aktive Oracle-DATABASE-/WCF-SAP-Ziele. |
| `biztalk_endpoints_best_effort_ignored` | Fehlgeschlagene Best-Effort-Probes, die vereinbarungsgemäß keinen Alarm auslösen. |
| `biztalk_endpoints_manual_overrides` | Aktive Ziele, die bewusst über einen geheimnisfreien manuellen Katalogeintrag aufgelöst werden. | | `biztalk_endpoints_manual_overrides` | Aktive Ziele, die bewusst über einen geheimnisfreien manuellen Katalogeintrag aufgelöst werden. |
| `biztalk_endpoints_inactive_skipped` | Konfigurierte Einträge, deren BizTalk-Artefakt aktuell nicht aktiv ist. | | `biztalk_endpoints_inactive_skipped` | Konfigurierte Einträge, deren BizTalk-Artefakt aktuell nicht aktiv ist. |
| `biztalk_endpoint_probe_duration_ms` | Gesamtlaufzeit der parallelisierten Socket-Probes in Millisekunden. | | `biztalk_endpoint_probe_duration_ms` | Gesamtlaufzeit der parallelisierten Socket-Probes in Millisekunden. |
+14 -1
View File
@@ -34,6 +34,7 @@ BizTalk-Runtimezustand gefiltert:
- Receive Location nur bei `IsDisabled=false` - Receive Location nur bei `IsDisabled=false`
- Send Port nur bei `Status=Started` - Send Port nur bei `Status=Started`
- dynamischer Send Port nie als statisches Ziel - dynamischer Send Port nie als statisches Ziel
- Scheduling-Komponente nie als Netzwerkziel
Damit wird ein inzwischen gestopptes/deaktiviertes Artefakt sofort nicht mehr Damit wird ein inzwischen gestopptes/deaktiviertes Artefakt sofort nicht mehr
geprüft. Prüffähige aktuelle WMI-Adressen werden bei jedem Minutenlauf direkt geprüft. Prüffähige aktuelle WMI-Adressen werden bei jedem Minutenlauf direkt
@@ -71,7 +72,7 @@ werden nur:
- Adaptername - Adaptername
- `TCP` oder `UDP` - `TCP` oder `UDP`
- Host und Port - Host und Port
- `enabled` und `autoDiscovered` - `enabled`, `autoDiscovered` und die Best-Effort-Kennzeichnung
URI-Benutzerinformationen, Kennwörter, Pfade und Querystrings werden nicht URI-Benutzerinformationen, Kennwörter, Pfade und Querystrings werden nicht
geschrieben. Der Katalog liegt im bereits ACL-geschützten `data`-Verzeichnis; geschrieben. Der Katalog liegt im bereits ACL-geschützten `data`-Verzeichnis;
@@ -135,6 +136,18 @@ passende nebenwirkungsfreie Netzwerktest. Er bestätigt DNS, Routing,
Firewall und einen annehmenden TCP-Port, aber nicht die fachliche Antwort der Firewall und einen annehmenden TCP-Port, aber nicht die fachliche Antwort der
Zielanwendung. Zielanwendung.
Oracle-`DATABASE`-Adressen mit explizitem Anbietersegment `/Oracle` verwenden
den angegebenen Port oder den Oracle-Standard TCP 1521. WCF-SAP-Adressen
verwenden vorrangig den ersten `SAPROUTER=/H/host[/S/port]`-Hop, andernfalls
`ListenerGwHost`/`ListenerGwServ` beziehungsweise `GwHost`/`GwServ`.
`sapgwNN` wird als TCP `33NN` abgeleitet; der SAProuter-Standard ist TCP 3299.
Diese automatisch erkannten Adapterziele sind `bestEffort="true"`: Die Probe
wird ausgeführt und gemessen, ein Fehlschlag aber nur in
`biztalk_endpoints_best_effort_ignored` gezählt. Nicht sicher ableitbare
DATABASE-/WCF-SAP-Adressen werden erwartbar ausgenommen. Ein manueller
Katalogeintrag bleibt standardmäßig streng alarmierend und ist die bewusste
Option für verbindliche Überwachung.
UDP ist verbindungslos. Bei `udp://host:port` bestätigt der Check nur DNS, UDP ist verbindungslos. Bei `udp://host:port` bestätigt der Check nur DNS,
Route und erfolgreichen lokalen Datagrammversand. Für einen belastbaren Route und erfolgreichen lokalen Datagrammversand. Für einen belastbaren
UDP-Dienstcheck wäre ein adapter-/protokollspezifisches Request/Response- UDP-Dienstcheck wäre ein adapter-/protokollspezifisches Request/Response-
+5 -2
View File
@@ -61,15 +61,18 @@ Artefakte. Nur die Textliste ist begrenzt.
Gesund, ohne Auflistung aller Ziele: Gesund, ohne Auflistung aller Ziele:
```text ```text
0 "BizTalk ACC Endpoint Reachability" biztalk_endpoints_configured=63;;;0|biztalk_endpoints_active=61;;;0|biztalk_endpoints_unique_targets=44;;;0|biztalk_endpoints_tested=61;;;0|biztalk_endpoints_available=61;;;0|biztalk_endpoints_failed=0;;1;0|biztalk_endpoints_unresolved=0;;1;0|biztalk_endpoints_expected_non_socket=12;;;0|biztalk_endpoints_manual_overrides=0;;;0|biztalk_endpoints_inactive_skipped=2;;;0|biztalk_endpoint_probe_duration_ms=842;;;0 Alle 61 aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar (unique_targets=44, expected_non_socket=12, manual_overrides=0, probe_ms=842); catalog_utc=2026-08-04T10:00:00.0000000Z 0 "BizTalk ACC Endpoint Reachability" biztalk_endpoints_configured=63;;;0|biztalk_endpoints_active=61;;;0|biztalk_endpoints_unique_targets=44;;;0|biztalk_endpoints_tested=61;;;0|biztalk_endpoints_available=60;;;0|biztalk_endpoints_failed=0;;1;0|biztalk_endpoints_unresolved=0;;1;0|biztalk_endpoints_expected_non_socket=12;;;0|biztalk_endpoints_best_effort=9;;;0|biztalk_endpoints_best_effort_ignored=1;;;0|biztalk_endpoints_manual_overrides=0;;;0|biztalk_endpoints_inactive_skipped=2;;;0|biztalk_endpoint_probe_duration_ms=842;;;0 Alle regulaer alarmierenden aktiven Send-/Receive-Endpunkte sind erreichbar (active=61, available=60, unique_targets=44, expected_non_socket=12, best_effort=9, best_effort_ignored=1, manual_overrides=0, probe_ms=842); catalog_utc=2026-08-04T10:00:00.0000000Z
``` ```
Fehlerhaft, nur mit nicht erreichbaren Zielen: Fehlerhaft, nur mit nicht erreichbaren Zielen:
```text ```text
2 "BizTalk ACC Endpoint Reachability" ... Endpoint reachability active=61, tested=61, available=59, failed=2, unresolved=0, expected_non_socket=12, manual_overrides=0; unavailable=SendPort:Orders\SP_Order_API[Primary]->orders.example:443/TCP(TCP timeout), ReceiveLocation:Invoices\RL_SFTP[Inbound]->sftp.example:22/TCP(SocketError=ConnectionRefused) 2 "BizTalk ACC Endpoint Reachability" ... Endpoint reachability active=61, tested=61, available=58, failed=2, unresolved=0, expected_non_socket=12, best_effort=9, best_effort_ignored=1, manual_overrides=0; unavailable=SendPort:Orders\SP_Order_API[Primary]->orders.example:443/TCP(TCP timeout), ReceiveLocation:Invoices\RL_SFTP[Inbound]->sftp.example:22/TCP(SocketError=ConnectionRefused)
``` ```
Der nicht verfügbare Best-Effort-Endpunkt wird nur gezählt und nicht in
`unavailable=` ausgegeben. Die beiden regulären Fehler bleiben CRIT.
## Fehlender oder veralteter Provider-Snapshot ## Fehlender oder veralteter Provider-Snapshot
Alle neun Services werden `UNKNOWN`, zum Beispiel: Alle neun Services werden `UNKNOWN`, zum Beispiel:
+3
View File
@@ -132,6 +132,9 @@ Soll:
- Receive Locations und Send Ports zeigen getrennte expected/unexpected Werte. - Receive Locations und Send Ports zeigen getrennte expected/unexpected Werte.
- Endpoint Reachability zeigt bei Erfolg nur die Gesamtzahl und bei Fehlern - Endpoint Reachability zeigt bei Erfolg nur die Gesamtzahl und bei Fehlern
ausschließlich nicht erreichbare oder wirklich unaufgelöste Ziele. ausschließlich nicht erreichbare oder wirklich unaufgelöste Ziele.
- Schedule ist vollständig ausgenommen; Oracle-DATABASE und WCF-SAP laufen
als TCP-Best-Effort und können keinen Alarm auslösen. Die zugehörigen
Erfolgs-/Ignoriert-Zähler bleiben für die Betriebsanalyse sichtbar.
- keine berechtigungsbedingten `UNKNOWN`-Services. - keine berechtigungsbedingten `UNKNOWN`-Services.
## Alarmierung der Transportkette ## Alarmierung der Transportkette
@@ -0,0 +1,101 @@
# RTM-Adapterregeln für Endpoint Reachability
## Anlass
Die ACC-Ausgabe vom 11.08.2026 zeigte `BizTalk Endpoint Reachability =
UNKNOWN`, obwohl die eigentlichen Socket-Probes in wenigen Millisekunden
abgeschlossen waren. Die Detailausgabe bestand überwiegend aus drei
Adapterklassen, die der generische URI-Parser nicht fachgerecht einordnen
konnte:
- Scheduling-Komponenten ohne externes Netzwerkziel,
- kundenspezifische `DATABASE`-Adressen, nahezu ausschließlich Oracle,
- WCF-SAP-Verbindungs-URIs mit Gateway- und SAProuter-Parametern.
Diese Fälle dürfen den RTM-Service weder mit Auflösungslücken noch mit
Fehlalarmen belasten.
## Verbindliche Klassifizierung ab Version 2.2.7
### Scheduling
Adaptername oder Adresse mit `Schedule`/`Scheduler` wird vollständig als
erwartbar ausgenommen. Es erfolgt keine Netzwerkprobe und kein UNKNOWN.
### DATABASE / Oracle
Nur die eindeutig erkennbare Form
```text
Database://host[:port]/.../Oracle
```
wird automatisch aufgelöst. Ein expliziter Port gewinnt; andernfalls wird
TCP 1521 verwendet. Andere DATABASE-Anbieter oder nicht eindeutig lesbare
Adressen werden erwartbar ausgenommen. Es wird kein UDP-Test erzeugt.
Oracle dokumentiert TCP 1521 als Standardadresse des Listeners und nimmt bei
fehlender Portangabe im Easy-Connect-Format ebenfalls 1521 an:
- [Oracle: Configuring the Network Environment](https://docs.oracle.com/en/database/oracle/oracle-database/19/admqs/configuring-the-network-environment.html)
- [Oracle: Configuring and Administering Oracle Net Listener](https://docs.oracle.com/en/database/oracle/oracle-database/21/netag/configuring-and-administering-oracle-net-listener.html)
### WCF-SAP
Microsoft dokumentiert `ListenerGwHost`, `ListenerGwServ` und `SAPROUTER` als
Bestandteile der SAP-Verbindungs-URI. Der Parser verwendet in dieser
Reihenfolge:
1. erster SAProuter-Hop aus `/H/host/S/port`,
2. `ListenerGwHost` und `ListenerGwServ`,
3. `GwHost` und `GwServ` als Fallback.
Numerische Dienste werden direkt übernommen. `sapgwNN` wird auf TCP `33NN`
abgebildet. Fehlt bei einem SAProuter-Hop `/S/`, gilt der dokumentierte
Standard TCP 3299. URI-Escapes werden vor der Auswertung dekodiert; Kennwörter,
Program-ID, Pfade und Querystrings gelangen weiterhin nicht in Katalog, Log
oder Checkmk-Ausgabe.
Quellen:
- [Microsoft Learn: Create the SAP system connection URI](https://learn.microsoft.com/en-us/biztalk/adapters-and-accelerators/adapter-sap/create-the-sap-system-connection-uri)
- [SAP Help: Entries in the Services File](https://help.sap.com/docs/SLTOOLSET/6bc963751d0840f996c5c9d7a7f83473/bb9faaebe0c74a58bbcc259c83f59437.html)
- [SAP Help: SAProuter Host and Port](https://help.sap.com/docs/SAP_NETWEAVER_AS_ABAP_FOR_SOH_740/e245703406684d8a81812f4c6334eb2f/486b5b0cb74c07bee10000000a42189d.html)
## Best-Effort statt Alarm
Automatisch abgeleitete DATABASE-/WCF-SAP-Ziele erhalten im lokalen Katalog
`bestEffort="true"`. Sie werden normal dedupliziert und per TCP verbunden.
- Erfolg erhöht die getesteten und verfügbaren Zähler.
- Timeout, DNS- oder Verbindungsfehler erhöht
`biztalk_endpoints_best_effort_ignored`.
- Der Fehler erscheint nicht als `unavailable=`, erzeugt keinen Warn-Logeintrag
pro Minute und ändert den Checkmk-Status nicht.
- Eine nicht ableitbare Adapteradresse zählt zu
`biztalk_endpoints_expected_non_socket`, nicht zu `unresolved`.
Damit erfüllt der Check die gewünschte „versuchen, sonst ignorieren“-Semantik.
Soll ein solches Ziel später verbindlich alarmieren, wird ein manueller
Katalogeintrag mit `autoDiscovered="false"` und ohne
`bestEffort="true"` hinterlegt. Reguläre automatisch erkannte Ziele bleiben
ebenfalls streng: Ihre Nichterreichbarkeit ist weiterhin CRIT.
## Regression und Abnahme
Die automatisierten Tests decken Scheduler-Ausschluss, Oracle-Standard- und
Explizitport, nicht unterstützte DATABASE-Anbieter, WCF-SAP-Gateway,
URI-kodierten SAProuter, zielbasierte SAP-Konfiguration ohne ableitbaren Host,
Geheimnisschutz und die nicht alarmierende Best-Effort-Ausgabe ab.
Für die Windows-/ACC-Abnahme nach Installation von 2.2.7:
1. Im Provider-Log muss der erzwungene Katalogabgleich erfolgreich sein.
2. `BizTalk Endpoint Reachability` darf wegen Schedule, DATABASE oder WCF-SAP
nicht mehr UNKNOWN werden.
3. `biztalk_endpoints_unresolved` darf diese drei Klassen nicht enthalten.
4. `biztalk_endpoints_best_effort` zeigt die tatsächlich abgeleiteten Oracle-
und SAP-Ziele; nicht erreichbare davon erscheinen nur in
`biztalk_endpoints_best_effort_ignored`.
5. Ein absichtlich nicht erreichbares reguläres Testziel muss weiterhin CRIT
auslösen, damit die strenge Standardalarmierung belegt bleibt.
@@ -6,5 +6,5 @@ using System.Runtime.CompilerServices;
[assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")] [assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")]
[assembly: AssemblyCompany("BEW")] [assembly: AssemblyCompany("BEW")]
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")] [assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
[assembly: AssemblyVersion("2.2.6.0")] [assembly: AssemblyVersion("2.2.7.0")]
[assembly: AssemblyFileVersion("2.2.6.0")] [assembly: AssemblyFileVersion("2.2.7.0")]
@@ -357,6 +357,8 @@ namespace BizTalkCheckmkPulse
/// <summary> /// <summary>
/// Formatiert die Netzwerk-Erreichbarkeit aller aktuell aktiven, pruefbaren BizTalk-Endpunkte. /// Formatiert die Netzwerk-Erreichbarkeit aller aktuell aktiven, pruefbaren BizTalk-Endpunkte.
/// </summary> /// </summary>
/// <param name="result">Vollstaendiges Provider-Ergebnis mit Endpoint-Zustand.</param>
/// <returns>Eine syntaktisch gueltige Checkmk-Local-Check-Zeile.</returns>
private string FormatEndpointConnectivity(ProbeResult result) private string FormatEndpointConnectivity(ProbeResult result)
{ {
var endpointState = result.EndpointConnectivity; var endpointState = result.EndpointConnectivity;
@@ -378,7 +380,12 @@ namespace BizTalkCheckmkPulse
"Endpoint-Pruefung nicht verlaesslich moeglich. " + EmptyAsUnknown(endpointState.Failure)); "Endpoint-Pruefung nicht verlaesslich moeglich. " + EmptyAsUnknown(endpointState.Failure));
} }
var failed = endpointState.Results.Where(x => !x.Available).ToArray(); var failed = endpointState.Results
.Where(x => !x.Available && !x.Endpoint.BestEffort)
.ToArray();
var ignoredBestEffort = endpointState.Results
.Where(x => !x.Available && x.Endpoint.BestEffort)
.ToArray();
var available = endpointState.Results.Count(x => x.Available); var available = endpointState.Results.Count(x => x.Available);
var incomplete = endpointState.Results.Count != endpointState.Active var incomplete = endpointState.Results.Count != endpointState.Active
|| endpointState.UnresolvedActive > 0 || endpointState.UnresolvedActive > 0
@@ -391,7 +398,7 @@ namespace BizTalkCheckmkPulse
: CheckState.Ok; : CheckState.Ok;
var metrics = string.Format( var metrics = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_unique_targets={2};;;0|biztalk_endpoints_tested={3};;;0|biztalk_endpoints_available={4};;;0|biztalk_endpoints_failed={5};;1;0|biztalk_endpoints_unresolved={6};;1;0|biztalk_endpoints_expected_non_socket={7};;;0|biztalk_endpoints_manual_overrides={8};;;0|biztalk_endpoints_inactive_skipped={9};;;0|biztalk_endpoint_probe_duration_ms={10};;;0", "biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_unique_targets={2};;;0|biztalk_endpoints_tested={3};;;0|biztalk_endpoints_available={4};;;0|biztalk_endpoints_failed={5};;1;0|biztalk_endpoints_unresolved={6};;1;0|biztalk_endpoints_expected_non_socket={7};;;0|biztalk_endpoints_best_effort={8};;;0|biztalk_endpoints_best_effort_ignored={9};;;0|biztalk_endpoints_manual_overrides={10};;;0|biztalk_endpoints_inactive_skipped={11};;;0|biztalk_endpoint_probe_duration_ms={12};;;0",
endpointState.Configured, endpointState.Configured,
endpointState.Active, endpointState.Active,
endpointState.UniqueTargets, endpointState.UniqueTargets,
@@ -400,15 +407,21 @@ namespace BizTalkCheckmkPulse
failed.Length, failed.Length,
endpointState.UnresolvedActive, endpointState.UnresolvedActive,
endpointState.ExpectedNonProbeableActive, endpointState.ExpectedNonProbeableActive,
endpointState.BestEffortActive,
ignoredBestEffort.Length,
endpointState.ManualOverridesActive, endpointState.ManualOverridesActive,
endpointState.SkippedInactive, endpointState.SkippedInactive,
endpointState.ProbeDurationMilliseconds); endpointState.ProbeDurationMilliseconds);
var detail = new StringBuilder(); var detail = new StringBuilder();
if (failed.Length == 0 && !incomplete) if (failed.Length == 0 && !incomplete)
{ {
detail.Append("Alle ").Append(endpointState.Active).Append(" aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar") detail.Append("Alle regulaer alarmierenden aktiven Send-/Receive-Endpunkte sind erreichbar")
.Append(" (unique_targets=").Append(endpointState.UniqueTargets) .Append(" (active=").Append(endpointState.Active)
.Append(", available=").Append(available)
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive) .Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
.Append(", best_effort=").Append(endpointState.BestEffortActive)
.Append(", best_effort_ignored=").Append(ignoredBestEffort.Length)
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive) .Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds).Append(")"); .Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds).Append(")");
} }
@@ -420,6 +433,8 @@ namespace BizTalkCheckmkPulse
.Append(", failed=").Append(failed.Length) .Append(", failed=").Append(failed.Length)
.Append(", unresolved=").Append(endpointState.UnresolvedActive) .Append(", unresolved=").Append(endpointState.UnresolvedActive)
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive) .Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
.Append(", best_effort=").Append(endpointState.BestEffortActive)
.Append(", best_effort_ignored=").Append(ignoredBestEffort.Length)
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive) .Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
.Append(", unique_targets=").Append(endpointState.UniqueTargets) .Append(", unique_targets=").Append(endpointState.UniqueTargets)
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds); .Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds);
@@ -13,9 +13,17 @@ namespace BizTalkCheckmkPulse
@"^(?<host>\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):(?<port>\d{1,5})(?:[/\\].*)?$", @"^(?<host>\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):(?<port>\d{1,5})(?:[/\\].*)?$",
RegexOptions.CultureInvariant); RegexOptions.CultureInvariant);
private static readonly Regex DatabaseProviderPattern = new Regex(
@"/(?:Oracle|SQLServer)(?:[/;?]|$)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
/// <summary> /// <summary>
/// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag. /// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag.
/// </summary> /// </summary>
/// <param name="candidate">Aktiver BizTalk-Transport mit Adaptername und Adresse.</param>
/// <param name="entry">Geheimnisfreier Katalogeintrag, sofern ein Ziel bestimmt wurde.</param>
/// <param name="reason">Begruendung, wenn kein pruefbares Ziel bestimmt wurde.</param>
/// <returns><see langword="true"/>, wenn Host, Port und Protokoll sicher bestimmt wurden.</returns>
public static bool TryCreate( public static bool TryCreate(
EndpointCandidate candidate, EndpointCandidate candidate,
out EndpointCatalogEntry entry, out EndpointCatalogEntry entry,
@@ -30,6 +38,8 @@ namespace BizTalkCheckmkPulse
/// <summary> /// <summary>
/// Trennt sicher pruefbare Ziele von erwartbar lokalen/adapterinternen Adressen und echten Aufloesungsluecken. /// Trennt sicher pruefbare Ziele von erwartbar lokalen/adapterinternen Adressen und echten Aufloesungsluecken.
/// </summary> /// </summary>
/// <param name="candidate">Zu klassifizierender BizTalk-Transport.</param>
/// <returns>Klassifizierung sowie gegebenenfalls das pruefbare Socket-Ziel.</returns>
public static EndpointResolution Analyze(EndpointCandidate candidate) public static EndpointResolution Analyze(EndpointCandidate candidate)
{ {
if (candidate == null) if (candidate == null)
@@ -42,6 +52,13 @@ namespace BizTalkCheckmkPulse
return Expected("Dynamischer Send Port besitzt kein statisch pruefbares Ziel."); return Expected("Dynamischer Send Port besitzt kein statisch pruefbares Ziel.");
} }
// Scheduling-Adapter bilden zeitliche BizTalk-Steuerung ab und besitzen kein
// fachlich relevantes Remoteziel. Sie werden bewusst vollstaendig ausgeklammert.
if (IsScheduler(candidate))
{
return Expected("Scheduling-Komponente ist von der Endpoint-Pruefung ausgenommen.");
}
var address = (candidate.Address ?? string.Empty).Trim(); var address = (candidate.Address ?? string.Empty).Trim();
if (address.Length == 0) if (address.Length == 0)
{ {
@@ -80,6 +97,33 @@ namespace BizTalkCheckmkPulse
return Expected("Relative Receive-Listener-Adresse enthaelt keine eindeutige Site-Bindung."); return Expected("Relative Receive-Listener-Adresse enthaelt keine eindeutige Site-Bindung.");
} }
// DATABASE und WCF-SAP liefern Adapteradressen, deren Socket-Ziel nicht von
// System.Uri abgeleitet werden kann. Eine fehlende sichere Ableitung ist bei
// diesen Best-Effort-Adaptern kein Abdeckungsfehler.
if (IsDatabaseAdapter(candidate))
{
EndpointCatalogEntry databaseEntry;
string databaseReason;
if (TryResolveDatabase(candidate, address, out databaseEntry, out databaseReason))
{
return Probeable(databaseEntry);
}
return Expected(databaseReason);
}
if (IsWcfSapAdapter(candidate))
{
EndpointCatalogEntry sapEntry;
string sapReason;
if (TryResolveWcfSap(candidate, address, out sapEntry, out sapReason))
{
return Probeable(sapEntry);
}
return Expected(sapReason);
}
EndpointCatalogEntry entry; EndpointCatalogEntry entry;
string protocol; string protocol;
string host; string host;
@@ -115,8 +159,306 @@ namespace BizTalkCheckmkPulse
Host = host, Host = host,
Port = port, Port = port,
Enabled = true, Enabled = true,
AutoDiscovered = true AutoDiscovered = true,
BestEffort = false
}; };
return Probeable(entry);
}
/// <summary>
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
/// </summary>
/// <param name="candidate">Zu klassifizierender BizTalk-Transport.</param>
/// <returns><see langword="true"/> nur bei einer echten Aufloesungsluecke.</returns>
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
{
return Analyze(candidate).Status == EndpointResolutionStatus.Unresolved;
}
/// <summary>
/// Leitet das Oracle-TCP-Ziel aus der expliziten DATABASE-Adresse ab.
/// </summary>
/// <param name="candidate">BizTalk-Artefakt, aus dem der Katalogeintrag aufgebaut wird.</param>
/// <param name="address">Vollstaendige DATABASE-Transportadresse.</param>
/// <param name="entry">Abgeleiteter geheimnisfreier Best-Effort-Eintrag.</param>
/// <param name="reason">Begruendung, wenn Host oder Anbieter nicht sicher erkennbar ist.</param>
/// <returns><see langword="true"/>, wenn ein Oracle-Host und TCP-Port bestimmt wurden.</returns>
private static bool TryResolveDatabase(
EndpointCandidate candidate,
string address,
out EndpointCatalogEntry entry,
out string reason)
{
entry = null;
reason = "DATABASE-Adresse enthaelt keinen sicher bestimmbaren Oracle-Listener.";
if (!address.StartsWith("database://", StringComparison.OrdinalIgnoreCase)
|| !DatabaseProviderPattern.IsMatch(address))
{
return false;
}
Uri uri;
if (!Uri.TryCreate(address, UriKind.Absolute, out uri)
|| string.IsNullOrWhiteSpace(uri.Host))
{
return false;
}
var oracle = Regex.IsMatch(
address,
@"/Oracle(?:[/;?]|$)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!oracle)
{
reason = "Nicht-Oracle-DATABASE-Ziel wird ohne explizite Betriebsfreigabe nicht automatisch geprueft.";
return false;
}
var port = HasExplicitPort(address) ? uri.Port : 1521;
string validationReason;
var host = NormalizeHost(uri.Host);
if (!Validate(host, port, out validationReason))
{
reason = validationReason;
return false;
}
entry = CreateAdapterEntry(candidate, host, port);
reason = string.Empty;
return true;
}
/// <summary>
/// Leitet den ersten erreichbaren TCP-Hop einer WCF-SAP-Verbindung ab.
/// </summary>
/// <param name="candidate">BizTalk-Artefakt, aus dem der Katalogeintrag aufgebaut wird.</param>
/// <param name="address">Vollstaendige SAP-Verbindungs-URI.</param>
/// <param name="entry">Abgeleiteter geheimnisfreier Best-Effort-Eintrag.</param>
/// <param name="reason">Begruendung, wenn Gateway oder SAProuter nicht sicher bestimmbar ist.</param>
/// <returns><see langword="true"/>, wenn ein SAProuter- oder Gateway-Ziel bestimmt wurde.</returns>
private static bool TryResolveWcfSap(
EndpointCandidate candidate,
string address,
out EndpointCatalogEntry entry,
out string reason)
{
entry = null;
reason = "WCF-SAP-Adresse enthaelt keinen sicher bestimmbaren Gateway- oder SAProuter-Port.";
var decoded = DecodeAdapterAddress(address);
string route;
if (TryReadParameter(decoded, "SAPROUTER", out route))
{
string routerHost;
int routerPort;
if (TryParseSapRouter(route, out routerHost, out routerPort))
{
entry = CreateAdapterEntry(candidate, routerHost, routerPort);
reason = string.Empty;
return true;
}
}
string gatewayHost;
string gatewayService;
if ((!TryReadParameter(decoded, "ListenerGwHost", out gatewayHost)
|| !TryReadParameter(decoded, "ListenerGwServ", out gatewayService))
&& (!TryReadParameter(decoded, "GwHost", out gatewayHost)
|| !TryReadParameter(decoded, "GwServ", out gatewayService)))
{
return false;
}
int gatewayPort;
if (!TryResolveSapService(gatewayService, out gatewayPort))
{
return false;
}
gatewayHost = NormalizeHost(gatewayHost);
string validationReason;
if (!Validate(gatewayHost, gatewayPort, out validationReason))
{
reason = validationReason;
return false;
}
entry = CreateAdapterEntry(candidate, gatewayHost, gatewayPort);
reason = string.Empty;
return true;
}
/// <summary>
/// Baut einen TCP-Katalogeintrag fuer einen automatisch abgeleiteten Best-Effort-Adapter.
/// </summary>
/// <param name="candidate">Quellartefakt fuer Metadaten und stabilen Schluessel.</param>
/// <param name="host">Bereinigter DNS-Name oder IP-Adresse.</param>
/// <param name="port">Gueltiger TCP-Zielport.</param>
/// <returns>Automatisch verwalteter, nicht alarmierender Katalogeintrag.</returns>
private static EndpointCatalogEntry CreateAdapterEntry(EndpointCandidate candidate, string host, int port)
{
return new EndpointCatalogEntry
{
Key = candidate.Key,
ArtifactType = candidate.ArtifactType,
ApplicationName = candidate.ApplicationName,
ArtifactName = candidate.ArtifactName,
TransportRole = candidate.TransportRole,
AdapterName = candidate.AdapterName,
Protocol = "TCP",
Host = host,
Port = port,
Enabled = true,
AutoDiscovered = true,
BestEffort = true
};
}
/// <summary>
/// Liest einen URI-Parameter auch aus SAP-URIs mit wiederholtem Fragezeichen.
/// </summary>
/// <param name="address">Dekodierte SAP-Verbindungsadresse.</param>
/// <param name="name">Name des gesuchten Query-Parameters.</param>
/// <param name="value">Bereinigter Parameterwert.</param>
/// <returns><see langword="true"/>, wenn ein nicht leerer Wert gefunden wurde.</returns>
private static bool TryReadParameter(string address, string name, out string value)
{
value = string.Empty;
var match = Regex.Match(
address ?? string.Empty,
@"(?:^|[?&])" + Regex.Escape(name) + @"=(?<value>[^?&]+)",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!match.Success)
{
return false;
}
value = match.Groups["value"].Value.Trim();
return value.Length > 0;
}
/// <summary>
/// Dekodiert URI-Escapes, ohne eine fehlerhafte Adapteradresse zum Laufzeitfehler zu machen.
/// </summary>
/// <param name="address">Rohe BizTalk-Transportadresse.</param>
/// <returns>Bestmoeglich dekodierte Adresse.</returns>
private static string DecodeAdapterAddress(string address)
{
var normalized = (address ?? string.Empty).Replace("&amp;", "&");
try
{
return Uri.UnescapeDataString(normalized);
}
catch (UriFormatException)
{
return normalized;
}
}
/// <summary>
/// Bestimmt Host und Port des ersten SAProuter-Hops einer Route.
/// </summary>
/// <param name="route">SAProuter-Route im Format <c>/H/host/S/port</c>.</param>
/// <param name="host">Host des ersten Router-Hops.</param>
/// <param name="port">Expliziter Port oder SAProuter-Standardport 3299.</param>
/// <returns><see langword="true"/>, wenn der erste Hop gueltig ist.</returns>
private static bool TryParseSapRouter(string route, out string host, out int port)
{
host = string.Empty;
port = 0;
var match = Regex.Match(
route ?? string.Empty,
@"/H/(?<host>[^/]+)(?:/S/(?<service>[^/]+))?",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
if (!match.Success)
{
return false;
}
host = NormalizeHost(match.Groups["host"].Value);
port = 3299;
var service = match.Groups["service"].Value;
if (service.Length > 0 && !TryResolveSapService(service, out port))
{
return false;
}
string reason;
return Validate(host, port, out reason);
}
/// <summary>
/// Uebersetzt numerische SAP-Dienste und sapgwNN in einen TCP-Port.
/// </summary>
/// <param name="service">Numerischer Port oder SAP-Gateway-Dienstname.</param>
/// <param name="port">Aufgeloester TCP-Port.</param>
/// <returns><see langword="true"/> bei einem gueltigen Port oder <c>sapgwNN</c>.</returns>
private static bool TryResolveSapService(string service, out int port)
{
port = 0;
var normalized = (service ?? string.Empty).Trim();
if (int.TryParse(normalized, out port))
{
return port >= 1 && port <= 65535;
}
var match = Regex.Match(
normalized,
@"^sapgw(?<instance>\d{2})$",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
int instance;
if (!match.Success || !int.TryParse(match.Groups["instance"].Value, out instance))
{
return false;
}
port = 3300 + instance;
return true;
}
/// <summary>
/// Erkennt BizTalk-Scheduling-Komponenten, die kein Remoteziel darstellen.
/// </summary>
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
/// <returns><see langword="true"/> fuer Schedule-/Scheduler-Konfigurationen.</returns>
private static bool IsScheduler(EndpointCandidate candidate)
{
return Contains(candidate.AdapterName, "SCHEDUL")
|| (candidate.Address ?? string.Empty).StartsWith("schedule", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Erkennt den kundenspezifischen DATABASE-Adapter und dessen URI-Schema.
/// </summary>
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
/// <returns><see langword="true"/> fuer DATABASE-Konfigurationen.</returns>
private static bool IsDatabaseAdapter(EndpointCandidate candidate)
{
return Contains(candidate.AdapterName, "DATABASE")
|| (candidate.Address ?? string.Empty).StartsWith("database://", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Erkennt WCF-SAP-Adapter, ohne beliebige Adapter mit SAP im Namen einzubeziehen.
/// </summary>
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
/// <returns><see langword="true"/> fuer eindeutig erkannte WCF-SAP-Konfigurationen.</returns>
private static bool IsWcfSapAdapter(EndpointCandidate candidate)
{
return Contains(candidate.AdapterName, "WCF-SAP")
|| Contains(candidate.AdapterName, "WCF SAP")
// BizTalk meldet den SAP Binding-Typ in manchen Umgebungen nur als
// WCF-Custom; das eindeutige sap://-Schema bleibt dennoch erhalten.
|| (candidate.Address ?? string.Empty).StartsWith("sap://", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Verpackt einen gueltigen Katalogeintrag als pruefbare Aufloesung.
/// </summary>
/// <param name="entry">Vollstaendig validierter Katalogeintrag.</param>
/// <returns>Pruefbare Endpoint-Aufloesung ohne Fehlergrund.</returns>
private static EndpointResolution Probeable(EndpointCatalogEntry entry)
{
return new EndpointResolution return new EndpointResolution
{ {
Status = EndpointResolutionStatus.Probeable, Status = EndpointResolutionStatus.Probeable,
@@ -125,14 +467,6 @@ namespace BizTalkCheckmkPulse
}; };
} }
/// <summary>
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
/// </summary>
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
{
return Analyze(candidate).Status == EndpointResolutionStatus.Unresolved;
}
private static bool TryResolve( private static bool TryResolve(
string address, string address,
string adapterName, string adapterName,
@@ -20,6 +20,12 @@ namespace BizTalkCheckmkPulse
private readonly int _maxBytes; private readonly int _maxBytes;
private readonly int _maxEntries; private readonly int _maxEntries;
/// <summary>
/// Initialisiert den validierenden XML-Katalogspeicher.
/// </summary>
/// <param name="path">Absoluter Zielpfad der Katalogdatei.</param>
/// <param name="maxBytes">Maximal akzeptierte Dateigroesse.</param>
/// <param name="maxEntries">Maximal akzeptierte Anzahl Eintraege.</param>
public EndpointCatalogStore(string path, int maxBytes, int maxEntries) public EndpointCatalogStore(string path, int maxBytes, int maxEntries)
{ {
_path = path; _path = path;
@@ -27,6 +33,11 @@ namespace BizTalkCheckmkPulse
_maxEntries = maxEntries; _maxEntries = maxEntries;
} }
/// <summary>
/// Liest und validiert einen maschinen- und umgebungsgebundenen Katalog.
/// </summary>
/// <param name="environmentName">Erwartete Monitoring-Umgebung.</param>
/// <returns>Vollstaendig validierter Endpoint-Katalog.</returns>
public EndpointCatalog Read(string environmentName) public EndpointCatalog Read(string environmentName)
{ {
var info = new FileInfo(_path); var info = new FileInfo(_path);
@@ -112,7 +123,9 @@ namespace BizTalkCheckmkPulse
Host = ReadAttribute(node, "host"), Host = ReadAttribute(node, "host"),
Port = ReadInt(node, "port", 1, 65535), Port = ReadInt(node, "port", 1, 65535),
Enabled = ReadBool(node, "enabled"), Enabled = ReadBool(node, "enabled"),
AutoDiscovered = ReadBool(node, "autoDiscovered") AutoDiscovered = ReadBool(node, "autoDiscovered"),
// Das optionale Attribut haelt bestehende Kataloge der Version 1 lesbar.
BestEffort = ReadOptionalBool(node, "bestEffort", false)
}; };
ValidateEntry(entry); ValidateEntry(entry);
catalog.Entries.Add(entry); catalog.Entries.Add(entry);
@@ -126,6 +139,10 @@ namespace BizTalkCheckmkPulse
return catalog; return catalog;
} }
/// <summary>
/// Validiert und ersetzt den Endpoint-Katalog atomar.
/// </summary>
/// <param name="catalog">Zu persistierender geheimnisfreier Katalog.</param>
public void Write(EndpointCatalog catalog) public void Write(EndpointCatalog catalog)
{ {
if (catalog == null) if (catalog == null)
@@ -170,7 +187,8 @@ namespace BizTalkCheckmkPulse
new XAttribute("host", entry.Host), new XAttribute("host", entry.Host),
new XAttribute("port", entry.Port), new XAttribute("port", entry.Port),
new XAttribute("enabled", entry.Enabled), new XAttribute("enabled", entry.Enabled),
new XAttribute("autoDiscovered", entry.AutoDiscovered))); new XAttribute("autoDiscovered", entry.AutoDiscovered),
new XAttribute("bestEffort", entry.BestEffort)));
} }
var document = new XDocument(new XDeclaration("1.0", "utf-8", null), root); var document = new XDocument(new XDeclaration("1.0", "utf-8", null), root);
@@ -284,5 +302,29 @@ namespace BizTalkCheckmkPulse
return value; return value;
} }
/// <summary>
/// Liest ein optionales boolesches Attribut mit rueckwaertskompatiblem Standardwert.
/// </summary>
/// <param name="element">XML-Element mit dem optionalen Attribut.</param>
/// <param name="name">Name des Attributs.</param>
/// <param name="defaultValue">Rueckgabewert, wenn das Attribut fehlt.</param>
/// <returns>Gelesener Wert oder der angegebene Standardwert.</returns>
private static bool ReadOptionalBool(XElement element, string name, bool defaultValue)
{
var attribute = element.Attribute(name);
if (attribute == null)
{
return defaultValue;
}
bool value;
if (!bool.TryParse(attribute.Value.Trim(), out value))
{
throw new InvalidDataException("Endpoint catalog boolean is invalid: " + name);
}
return value;
}
} }
} }
@@ -18,12 +18,21 @@ namespace BizTalkCheckmkPulse
private readonly MonitoringOptions _options; private readonly MonitoringOptions _options;
private readonly FileLogger _logger; private readonly FileLogger _logger;
/// <summary>
/// Initialisiert die Endpoint-Pruefung mit Laufzeitgrenzen und Dateiprotokoll.
/// </summary>
/// <param name="options">Validierte Monitoring-Konfiguration.</param>
/// <param name="logger">Gemeinsames Provider-Dateiprotokoll.</param>
public EndpointConnectivityProbe(MonitoringOptions options, FileLogger logger) public EndpointConnectivityProbe(MonitoringOptions options, FileLogger logger)
{ {
_options = options; _options = options;
_logger = logger; _logger = logger;
} }
/// <summary>
/// Synchronisiert den Katalog und ergaenzt das Gesamtergebnis um Socket-Probes.
/// </summary>
/// <param name="result">Bereits mit aktuellen BizTalk-Artefakten gefuelltes Ergebnis.</param>
public void Query(ProbeResult result) public void Query(ProbeResult result)
{ {
var state = result.EndpointConnectivity; var state = result.EndpointConnectivity;
@@ -141,7 +150,13 @@ namespace BizTalkCheckmkPulse
foreach (var probeResult in probeResults) foreach (var probeResult in probeResults)
{ {
state.Results.Add(probeResult); state.Results.Add(probeResult);
if (!probeResult.Available) if (!probeResult.Available && probeResult.Endpoint.BestEffort)
{
// DATABASE-/WCF-SAP-Ziele sind bewusst Diagnose-Probes: Ein
// Fehlschlag darf weder Tageslog noch Checkmk mit Alarmen fluten.
state.BestEffortIgnoredFailures++;
}
else if (!probeResult.Available)
{ {
_logger.Warning( _logger.Warning(
"Endpoint unavailable. endpoint=" + Display(probeResult.Endpoint) "Endpoint unavailable. endpoint=" + Display(probeResult.Endpoint)
@@ -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( internal static EndpointCatalog SynchronizeCatalog(
EndpointCatalog existing, EndpointCatalog existing,
IEnumerable<EndpointCandidate> candidates, IEnumerable<EndpointCandidate> candidates,
@@ -223,6 +245,10 @@ namespace BizTalkCheckmkPulse
/// Ermittelt aus aktuellen WMI-Kandidaten und manuellen Overrides die in diesem Lauf zu pruefenden Ziele. /// Ermittelt aus aktuellen WMI-Kandidaten und manuellen Overrides die in diesem Lauf zu pruefenden Ziele.
/// Automatisch erkannte Katalogeintraege werden bewusst nicht als veraltete Laufzeitquelle verwendet. /// Automatisch erkannte Katalogeintraege werden bewusst nicht als veraltete Laufzeitquelle verwendet.
/// </summary> /// </summary>
/// <param name="catalog">Gueltiger lokaler Endpoint-Katalog.</param>
/// <param name="candidates">Aktuelle aktive und inaktive BizTalk-Transporte.</param>
/// <param name="state">Zu aktualisierende Laufzeitzaehler und Aufloesungsdiagnosen.</param>
/// <returns>Aktuell aktive, deduplizierte Katalogeintraege.</returns>
internal static EndpointCatalogEntry[] ResolveActiveEndpoints( internal static EndpointCatalogEntry[] ResolveActiveEndpoints(
EndpointCatalog catalog, EndpointCatalog catalog,
IEnumerable<EndpointCandidate> candidates, IEnumerable<EndpointCandidate> candidates,
@@ -284,10 +310,12 @@ namespace BizTalkCheckmkPulse
x.Enabled x.Enabled
&& !string.Equals(x.ArtifactType, "Manual", StringComparison.OrdinalIgnoreCase) && !string.Equals(x.ArtifactType, "Manual", StringComparison.OrdinalIgnoreCase)
&& !activeKeys.Contains(x.Key)); && !activeKeys.Contains(x.Key));
return selected var resolved = selected
.GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase) .GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First()) .Select(x => x.First())
.ToArray(); .ToArray();
state.BestEffortActive = resolved.Count(x => x.BestEffort);
return resolved;
} }
/// <summary> /// <summary>
@@ -356,6 +384,13 @@ namespace BizTalkCheckmkPulse
}).ToArray(); }).ToArray();
} }
/// <summary>
/// Berechnet die theoretische Obergrenze fuer vollstaendig timeoutende Zielgruppen.
/// </summary>
/// <param name="targetCount">Anzahl eindeutiger Socket-Ziele.</param>
/// <param name="concurrency">Maximale parallele Probes.</param>
/// <param name="timeoutMilliseconds">Timeout je Probe in Millisekunden.</param>
/// <returns>Theoretische Worst-Case-Dauer in Millisekunden.</returns>
internal static long CalculateWorstCaseProbeMilliseconds(int targetCount, int concurrency, int timeoutMilliseconds) internal static long CalculateWorstCaseProbeMilliseconds(int targetCount, int concurrency, int timeoutMilliseconds)
{ {
if (targetCount <= 0) if (targetCount <= 0)
@@ -465,6 +500,11 @@ namespace BizTalkCheckmkPulse
: "SocketError=" + socket.SocketErrorCode + ": " + socket.Message; : "SocketError=" + socket.SocketErrorCode + ": " + socket.Message;
} }
/// <summary>
/// Formatiert ein geheimnisfreies Socket-Ziel fuer Checkmk und Dateilog.
/// </summary>
/// <param name="endpoint">Zu formatierender Katalogeintrag.</param>
/// <returns>Artefakt, Host, Port und Protokoll ohne URI-Geheimnisse.</returns>
internal static string Display(EndpointCatalogEntry endpoint) internal static string Display(EndpointCatalogEntry endpoint)
{ {
var artifact = string.IsNullOrWhiteSpace(endpoint.ApplicationName) var artifact = string.IsNullOrWhiteSpace(endpoint.ApplicationName)
+21
View File
@@ -305,7 +305,20 @@ namespace BizTalkCheckmkPulse
public int Port { get; set; } public int Port { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
public bool AutoDiscovered { get; set; } public bool AutoDiscovered { get; set; }
/// <summary>
/// Gibt an, dass ein automatisch abgeleiteter Adaptertest nur Diagnosewert besitzt
/// und ein Fehlschlag deshalb keinen Checkmk-Alarm ausloest.
/// </summary>
public bool BestEffort { get; set; }
/// <summary>
/// Erstellt den stabilen, adressunabhaengigen Schluessel eines BizTalk-Transports.
/// </summary>
/// <param name="artifactType">Typ des BizTalk-Artefakts.</param>
/// <param name="applicationName">Name der BizTalk-Anwendung.</param>
/// <param name="artifactName">Name des Send Ports oder der Receive Location.</param>
/// <param name="transportRole">Primaer-, Sekundaer- oder Inbound-Rolle.</param>
/// <returns>Normalisierter Katalogschluessel.</returns>
public static string BuildKey(string artifactType, string applicationName, string artifactName, string transportRole) public static string BuildKey(string artifactType, string applicationName, string artifactName, string transportRole)
{ {
return NormalizeKeyPart(artifactType) return NormalizeKeyPart(artifactType)
@@ -371,6 +384,14 @@ namespace BizTalkCheckmkPulse
public int SkippedInactive { get; set; } public int SkippedInactive { get; set; }
public int UnresolvedActive { get; set; } public int UnresolvedActive { get; set; }
public int ExpectedNonProbeableActive { get; set; } public int ExpectedNonProbeableActive { get; set; }
/// <summary>
/// Anzahl der automatisch abgeleiteten, nicht alarmierenden Adapterziele.
/// </summary>
public int BestEffortActive { get; set; }
/// <summary>
/// Anzahl fehlgeschlagener Best-Effort-Probes im aktuellen Lauf.
/// </summary>
public int BestEffortIgnoredFailures { get; set; }
public int ManualOverridesActive { get; set; } public int ManualOverridesActive { get; set; }
public int UniqueTargets { get; set; } public int UniqueTargets { get; set; }
public long ProbeDurationMilliseconds { get; set; } public long ProbeDurationMilliseconds { get; set; }
+3 -1
View File
@@ -115,7 +115,9 @@ namespace BizTalkCheckmkPulse
+ " endpoints_active=" + " endpoints_active="
+ result.EndpointConnectivity.Active + result.EndpointConnectivity.Active
+ " endpoints_failed=" + " endpoints_failed="
+ result.EndpointConnectivity.Results.Count(x => !x.Available) + result.EndpointConnectivity.Results.Count(x => !x.Available && !x.Endpoint.BestEffort)
+ " endpoints_best_effort_ignored="
+ result.EndpointConnectivity.BestEffortIgnoredFailures
+ " endpoints_unresolved=" + " endpoints_unresolved="
+ result.EndpointConnectivity.UnresolvedActive + result.EndpointConnectivity.UnresolvedActive
+ " endpoints_expected_non_socket=" + " endpoints_expected_non_socket="
@@ -6,5 +6,5 @@ using System.Reflection;
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")] [assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
[assembly: AssemblyCompany("BEW")] [assembly: AssemblyCompany("BEW")]
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")] [assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
[assembly: AssemblyVersion("2.2.6.0")] [assembly: AssemblyVersion("2.2.7.0")]
[assembly: AssemblyFileVersion("2.2.6.0")] [assembly: AssemblyFileVersion("2.2.7.0")]
+109 -1
View File
@@ -30,11 +30,13 @@ namespace BizTalkCheckmkPulse.Tests
Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded); Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded);
Run("EndpointAddressesResolveWithoutSecrets", EndpointAddressesResolveWithoutSecrets); Run("EndpointAddressesResolveWithoutSecrets", EndpointAddressesResolveWithoutSecrets);
Run("AdapterSpecificEndpointAddressesResolve", AdapterSpecificEndpointAddressesResolve); Run("AdapterSpecificEndpointAddressesResolve", AdapterSpecificEndpointAddressesResolve);
Run("SchedulerDatabaseAndSapEndpointsAreClassifiedSafely", SchedulerDatabaseAndSapEndpointsAreClassifiedSafely);
Run("EndpointClassificationSeparatesExpectedAndUnresolved", EndpointClassificationSeparatesExpectedAndUnresolved); Run("EndpointClassificationSeparatesExpectedAndUnresolved", EndpointClassificationSeparatesExpectedAndUnresolved);
Run("EndpointCatalogPreservesManualOverrides", EndpointCatalogPreservesManualOverrides); Run("EndpointCatalogPreservesManualOverrides", EndpointCatalogPreservesManualOverrides);
Run("EndpointRuntimeUsesCurrentAddressAndManualOverrides", EndpointRuntimeUsesCurrentAddressAndManualOverrides); Run("EndpointRuntimeUsesCurrentAddressAndManualOverrides", EndpointRuntimeUsesCurrentAddressAndManualOverrides);
Run("EndpointCatalogRoundTrip", EndpointCatalogRoundTrip); Run("EndpointCatalogRoundTrip", EndpointCatalogRoundTrip);
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets); Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
Run("BestEffortEndpointFailuresRemainNonAlerting", BestEffortEndpointFailuresRemainNonAlerting);
Run("EndpointOutputExplainsOnlyRealResolutionGaps", EndpointOutputExplainsOnlyRealResolutionGaps); Run("EndpointOutputExplainsOnlyRealResolutionGaps", EndpointOutputExplainsOnlyRealResolutionGaps);
Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval); Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval);
Run("ForcedEndpointRefreshRewritesFreshCatalog", ForcedEndpointRefreshRewritesFreshCatalog); Run("ForcedEndpointRefreshRewritesFreshCatalog", ForcedEndpointRefreshRewritesFreshCatalog);
@@ -470,6 +472,73 @@ namespace BizTalkCheckmkPulse.Tests
AssertEqual(8080, endpoint.Port, "wildcard listener port"); AssertEqual(8080, endpoint.Port, "wildcard listener port");
} }
private static void SchedulerDatabaseAndSapEndpointsAreClassifiedSafely()
{
var candidate = new EndpointCandidate
{
ArtifactType = "ReceiveLocation",
ApplicationName = "Integration",
ArtifactName = "RL Scheduler",
TransportRole = "Inbound",
AdapterName = "Schedule",
Address = "schedule://weekday/06:00",
Active = true
};
AssertEqual(
EndpointResolutionStatus.ExpectedNonProbeable,
EndpointAddressParser.Analyze(candidate).Status,
"scheduler must be excluded");
candidate.ArtifactType = "SendPort";
candidate.ArtifactName = "SP Oracle";
candidate.TransportRole = "Primary";
candidate.AdapterName = "DATABASE";
candidate.Address = "Database://oracle01/SAPUSER/Oracle";
var resolution = EndpointAddressParser.Analyze(candidate);
AssertEqual(EndpointResolutionStatus.Probeable, resolution.Status, "Oracle DATABASE classification");
AssertEqual("oracle01", resolution.Entry.Host, "Oracle DATABASE host");
AssertEqual(1521, resolution.Entry.Port, "Oracle DATABASE default port");
Assert(resolution.Entry.BestEffort, "Oracle DATABASE probe must be best effort");
candidate.Address = "Database://oracle01:1526/SAPUSER/Oracle";
resolution = EndpointAddressParser.Analyze(candidate);
AssertEqual(1526, resolution.Entry.Port, "Oracle DATABASE explicit port");
candidate.Address = "Database://sql01/Lookup/SQLServer";
resolution = EndpointAddressParser.Analyze(candidate);
AssertEqual(
EndpointResolutionStatus.ExpectedNonProbeable,
resolution.Status,
"non-Oracle DATABASE endpoint must not cause UNKNOWN");
candidate.ArtifactType = "ReceiveLocation";
candidate.ArtifactName = "RL SAP";
candidate.TransportRole = "Inbound";
// WMI kann einen WCF-SAP-Port lediglich als WCF-Custom ausweisen;
// das sap://-Schema muss deshalb fuer die Klassifizierung genuegen.
candidate.AdapterName = "WCF-Custom";
candidate.Address = "sap://Client=800;lang=DE@A/sap-app/00?ListenerGwHost=sap-gateway.example&ListenerGwServ=SAPGW00&ListenerProgramId=SECRET";
resolution = EndpointAddressParser.Analyze(candidate);
AssertEqual(EndpointResolutionStatus.Probeable, resolution.Status, "WCF-SAP listener classification");
AssertEqual("sap-gateway.example", resolution.Entry.Host, "WCF-SAP gateway host");
AssertEqual(3300, resolution.Entry.Port, "WCF-SAP sapgw00 port");
Assert(resolution.Entry.BestEffort, "WCF-SAP probe must be best effort");
Assert(resolution.Entry.Host.IndexOf("SECRET", StringComparison.OrdinalIgnoreCase) < 0, "SAP Program ID must not enter catalog");
candidate.Address = "sap://Client=800;lang=DE@A/sap-app/00?SAPROUTER=%2fH%2fsap-router.example%2fS%2f3400%2fH%2fsap-gateway.example&ListenerGwHost=sap-gateway.example&ListenerGwServ=sapgw00";
resolution = EndpointAddressParser.Analyze(candidate);
AssertEqual("sap-router.example", resolution.Entry.Host, "SAProuter first hop host");
AssertEqual(3400, resolution.Entry.Port, "SAProuter explicit port");
candidate.Address = "sap://Client=800;lang=DE@D/DESTINATION";
resolution = EndpointAddressParser.Analyze(candidate);
AssertEqual(
EndpointResolutionStatus.ExpectedNonProbeable,
resolution.Status,
"destination-based WCF-SAP endpoint must not cause UNKNOWN");
}
private static void EndpointClassificationSeparatesExpectedAndUnresolved() private static void EndpointClassificationSeparatesExpectedAndUnresolved()
{ {
var candidate = new EndpointCandidate var candidate = new EndpointCandidate
@@ -617,6 +686,33 @@ namespace BizTalkCheckmkPulse.Tests
Assert(line.IndexOf("up.example.test", StringComparison.Ordinal) < 0, "reachable host must not clutter output"); Assert(line.IndexOf("up.example.test", StringComparison.Ordinal) < 0, "reachable host must not clutter output");
} }
private static void BestEffortEndpointFailuresRemainNonAlerting()
{
var result = new ProbeResult();
result.EndpointConnectivity.RuntimeStateAvailable = true;
result.EndpointConnectivity.CatalogAvailable = true;
result.EndpointConnectivity.RefreshSucceeded = true;
result.EndpointConnectivity.Configured = 1;
result.EndpointConnectivity.Active = 1;
result.EndpointConnectivity.BestEffortActive = 1;
var endpoint = TestEndpoint("SAP Listener", "sap-gateway.example");
endpoint.AdapterName = "WCF-SAP";
endpoint.Port = 3300;
endpoint.BestEffort = true;
result.EndpointConnectivity.Results.Add(new EndpointProbeResult
{
Endpoint = endpoint,
Available = false,
Failure = "TCP timeout"
});
var line = FindServiceLine(result, "Endpoint Reachability");
Assert(line.StartsWith("0 \"BizTalk Endpoint Reachability\"", StringComparison.Ordinal), "best-effort failure must remain OK");
Assert(line.Contains("biztalk_endpoints_failed=0"), "best-effort failure must not increment failed metric");
Assert(line.Contains("biztalk_endpoints_best_effort_ignored=1"), "ignored best-effort metric missing");
Assert(line.IndexOf("unavailable=", StringComparison.OrdinalIgnoreCase) < 0, "best-effort target must not appear as unavailable");
}
private static void EndpointOutputExplainsOnlyRealResolutionGaps() private static void EndpointOutputExplainsOnlyRealResolutionGaps()
{ {
var result = new ProbeResult(); var result = new ProbeResult();
@@ -652,7 +748,9 @@ namespace BizTalkCheckmkPulse.Tests
ActiveCandidates = 150, ActiveCandidates = 150,
UnresolvedCandidates = 7 UnresolvedCandidates = 7
}; };
catalog.Entries.Add(TestEndpoint("Orders", "api.example.test")); var storedEndpoint = TestEndpoint("Orders", "api.example.test");
storedEndpoint.BestEffort = true;
catalog.Entries.Add(storedEndpoint);
var store = new EndpointCatalogStore(path, 1048576, 100); var store = new EndpointCatalogStore(path, 1048576, 100);
store.Write(catalog); store.Write(catalog);
var loaded = store.Read("ACC"); var loaded = store.Read("ACC");
@@ -660,8 +758,18 @@ namespace BizTalkCheckmkPulse.Tests
AssertEqual(1, loaded.Entries.Count, "catalog entry count"); AssertEqual(1, loaded.Entries.Count, "catalog entry count");
AssertEqual("api.example.test", loaded.Entries[0].Host, "catalog host"); AssertEqual("api.example.test", loaded.Entries[0].Host, "catalog host");
AssertEqual(443, loaded.Entries[0].Port, "catalog port"); AssertEqual(443, loaded.Entries[0].Port, "catalog port");
Assert(loaded.Entries[0].BestEffort, "catalog best-effort marker");
AssertEqual(7, loaded.UnresolvedCandidates, "catalog unresolved candidate count"); AssertEqual(7, loaded.UnresolvedCandidates, "catalog unresolved candidate count");
Assert(File.ReadAllText(path).IndexOf("top-secret", StringComparison.OrdinalIgnoreCase) < 0, "catalog must not contain URI secrets"); Assert(File.ReadAllText(path).IndexOf("top-secret", StringComparison.OrdinalIgnoreCase) < 0, "catalog must not contain URI secrets");
// Vorhandene Kataloge der Formatversion 1 besitzen das neue optionale
// Attribut noch nicht und muessen beim Update weiterhin lesbar bleiben.
var legacyXml = File.ReadAllText(path)
.Replace(" bestEffort=\"true\"", string.Empty)
.Replace(" bestEffort=\"True\"", string.Empty);
File.WriteAllText(path, legacyXml, new UTF8Encoding(false));
loaded = store.Read("ACC");
Assert(!loaded.Entries[0].BestEffort, "legacy catalog best-effort default");
} }
finally finally
{ {