Harden RTM endpoint adapter handling
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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. |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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-
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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("&", "&");
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user