Expand BizTalk operational metrics

This commit is contained in:
2026-07-30 16:15:20 +02:00
parent d15bb6539b
commit 826d87fef7
17 changed files with 859 additions and 239 deletions
+95 -40
View File
@@ -12,7 +12,7 @@ Verbindliche Randbedingungen:
- Der Checkmk Windows Agent bleibt `LocalSystem`. - Der Checkmk Windows Agent bleibt `LocalSystem`.
- `LocalSystem` erhaelt keine BizTalk-/SQL-Gruppenmitgliedschaft. - `LocalSystem` erhaelt keine BizTalk-/SQL-Gruppenmitgliedschaft.
- Ein separates Dienstkonto oder bevorzugt gMSA sammelt minuetlich. - Ein normales dediziertes AD-Servicekonto sammelt minuetlich.
- Der Agentpfad fuehrt niemals WMI- oder SQL-Abfragen aus. - Der Agentpfad fuehrt niemals WMI- oder SQL-Abfragen aus.
- Fehler muessen als gueltige Checkmk-`UNKNOWN`-Services sichtbar werden. - Fehler muessen als gueltige Checkmk-`UNKNOWN`-Services sichtbar werden.
- Schreiben und Lesen duerfen nie einen halben Snapshot exponieren. - Schreiben und Lesen duerfen nie einen halben Snapshot exponieren.
@@ -46,7 +46,7 @@ Konto.
Privilegierte Zone Privilegierte Zone
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Task Scheduler: "BizTalk Checkmk Pulse Provider" │ │ Task Scheduler: "BizTalk Checkmk Pulse Provider" │
│ Konto: DOMAIN\svc_biztalk_cmk$ (gMSA empfohlen) │ Konto: DOMAIN\svc_biztalk_cmk (RunLevel Limited)
│ Intervall: 1 Minute, IgnoreNew, Laufzeitlimit 5 Minuten │ │ Intervall: 1 Minute, IgnoreNew, Laufzeitlimit 5 Minuten │
│ │ │ │
│ BizTalkCheckmkPulse.exe --collect │ │ BizTalkCheckmkPulse.exe --collect │
@@ -89,7 +89,7 @@ Ablauf:
2. Tageslogs gemaess `LogRetentionDays` bereinigen. 2. Tageslogs gemaess `LogRetentionDays` bereinigen.
3. exklusives Handle auf `<SnapshotPath>.provider.lock` halten. 3. exklusives Handle auf `<SnapshotPath>.provider.lock` halten.
4. WMI-, SQL- und Event-Log-Probes ausfuehren. 4. WMI-, SQL- und Event-Log-Probes ausfuehren.
5. sechs stabile und optionale dynamische Checkmk-Zeilen formatieren. 5. acht stabile und optionale dynamische Checkmk-Zeilen formatieren.
6. Snapshot in einer eindeutigen Temporaerdatei desselben Verzeichnisses 6. Snapshot in einer eindeutigen Temporaerdatei desselben Verzeichnisses
schreiben. schreiben.
7. `Flush(true)` ausfuehren und Temporaerdatei atomar publizieren. 7. `Flush(true)` ausfuehren und Temporaerdatei atomar publizieren.
@@ -132,19 +132,19 @@ Der Consumer:
- gibt bei Erfolg ausschliesslich den validierten Payload aus. - gibt bei Erfolg ausschliesslich den validierten Payload aus.
Er gibt immer Exitcode `0` zurueck, damit ein Fehler die komplette Er gibt immer Exitcode `0` zurueck, damit ein Fehler die komplette
Checkmk-Agentsektion nicht zerstoert. Jede Ablehnung erzeugt sechs Checkmk-Agentsektion nicht zerstoert. Jede Ablehnung erzeugt acht
`UNKNOWN`-Zeilen und einen Eintrag im Consumer-Log. `UNKNOWN`-Zeilen und einen Eintrag im Consumer-Log.
## 4. Snapshot-Vertrag ## 4. Snapshot-Vertrag
Version 1: Version 2:
```text ```text
BIZTALK_CHECKMK_PULSE_SNAPSHOT_V1 BIZTALK_CHECKMK_PULSE_SNAPSHOT_V2
generatedUtc=2026-07-30T12:34:56.1234567Z generatedUtc=2026-07-30T12:34:56.1234567Z
machineBase64=QVYyM0FHUFdCSU8x machineBase64=QVYyM0FHUFdCSU8x
identityBase64=QkVXXHN2Y19iaXp0YWxrX2NtayQ= identityBase64=QkVXXHN2Y19iaXp0YWxrX2NtayQ=
payloadLines=6 payloadLines=8
payloadSha256=<64 hex characters> payloadSha256=<64 hex characters>
0 "BizTalk Platform" - ... 0 "BizTalk Platform" - ...
@@ -202,7 +202,37 @@ Pflichtklassen:
Hostnamen werden clientseitig verglichen. Sonderzeichen und FQDN-/Kurzname Hostnamen werden clientseitig verglichen. Sonderzeichen und FQDN-/Kurzname
gelangen nicht in dynamisch erzeugte WQL-Filter. gelangen nicht in dynamisch erzeugte WQL-Filter.
### 5.2 SQL-Zugriffsprobe `MSBTS_ServiceInstance` wird fuer `ServiceStatus=4` (resumable),
`ServiceStatus=32` (non-resumable) und `ServiceClass=64` abgefragt.
`ServiceClass=64` kennzeichnet Routing Failure Reports. Sie werden als eigene
Metrik ausgewiesen und zugleich in `suspended_nonresumable` mitgezaehlt.
### 5.2 Kompaktes Checkmk-Servicebild
Die alte Sammelzeile `BizTalk Runtime Artifacts` wurde durch drei kurze
Services ersetzt. Insgesamt entstehen acht stabile Services:
1. `BizTalk Platform`
2. `BizTalk SQL Access`
3. `BizTalk Suspended Instances`
4. `BizTalk Host Instances`
5. `BizTalk Receive Locations`
6. `BizTalk Send Ports`
7. `BizTalk Orchestrations`
8. `BizTalk Event Log`
Receive Locations und Send Ports alarmieren nur fuer unerwartete
Aus-Zustaende. Fachlich bewusst deaktivierte Namen werden exakt, ohne
Wildcards, in `ExpectedDisabledReceiveLocations` beziehungsweise
`ExpectedInactiveSendPorts` hinterlegt. Ein Eintrag ist entweder nur der Name
oder `Anwendung\Name`.
Pro Summary werden maximal `MaxSummaryItems` Treffer angezeigt. Weitere
Artefakte werden als `(+n more)` zusammengefasst. `MaxDetailCharacters`
begrenzt jede Detailausgabe, damit Checkmk-Ansichten kompakt bleiben, waehrend
alle numerischen Metriken vollstaendig erhalten bleiben.
### 5.3 SQL-Zugriffsprobe
Der Provider oeffnet fuer Management- und Master-MessageBox-Datenbank eine Der Provider oeffnet fuer Management- und Master-MessageBox-Datenbank eine
kurze `System.Data.SqlClient`-Verbindung mit integrierter kurze `System.Data.SqlClient`-Verbindung mit integrierter
@@ -213,7 +243,7 @@ Die Ausgabe `execution_identity=` zeigt deshalb das Provider-Konto, nicht mehr
`NT AUTHORITY\SYSTEM`. Der Check beweist, dass genau das Scheduled-Task-Konto `NT AUTHORITY\SYSTEM`. Der Check beweist, dass genau das Scheduled-Task-Konto
die Ziele erreichen kann. die Ziele erreichen kann.
### 5.3 Event Log ### 5.4 Event Log
Der Provider liest das lokale Application Log im konfigurierten Zeitfenster Der Provider liest das lokale Application Log im konfigurierten Zeitfenster
und filtert Quellen wie `BizTalk Server`, `XLANG/s`, `ENTSSO`, und filtert Quellen wie `BizTalk Server`, `XLANG/s`, `ENTSSO`,
@@ -286,14 +316,17 @@ Sollkonfiguration:
| Programm | `%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe` | | Programm | `%ProgramFiles%\BizTalkCheckmkPulse\BizTalkCheckmkPulse.exe` |
| Argument | `--collect` | | Argument | `--collect` |
| Arbeitsverzeichnis | `%ProgramFiles%\BizTalkCheckmkPulse` | | Arbeitsverzeichnis | `%ProgramFiles%\BizTalkCheckmkPulse` |
| Benutzer | dediziertes Dienstkonto oder gMSA | | Benutzer | normales dediziertes AD-Servicekonto |
| Run level | `Limited`; keine lokale Administratorrolle erforderlich |
| Mehrfachinstanzen | `IgnoreNew` | | Mehrfachinstanzen | `IgnoreNew` |
| Laufzeitlimit | 5 Minuten | | Laufzeitlimit | 5 Minuten |
| StartWhenAvailable | aktiv | | StartWhenAvailable | aktiv |
| Restart | zweimal im Minutenabstand | | Restart | zweimal im Minutenabstand |
Der Task wird mit gespeichertem Dienstkontokennwort beziehungsweise gMSA Der Task wird mit gespeichertem Dienstkontokennwort ausgefuehrt, also
ausgefuehrt, also unabhaengig von einer interaktiven Anmeldung. unabhaengig von einer interaktiven Anmeldung. Bei Kennwortwechsel oder
-ablauf muss das Task-Kennwort aktualisiert werden. Ein gMSA ist weiterhin
optional unterstuetzt, aber nicht die produktive Standardannahme.
## 8. Installation ## 8. Installation
@@ -304,25 +337,7 @@ scripts\test-release.cmd
scripts\package-release.cmd scripts\package-release.cmd
``` ```
### 8.2 gMSA ### 8.2 Normales Servicekonto
Voraussetzungen:
1. gMSA in AD erstellen und Abrufrecht auf den BizTalk-Server begrenzen.
2. Konto lokal installieren und mit `Test-ADServiceAccount` pruefen.
3. gMSA in die exakt konfigurierte BizTalk-Read-Only-Gruppe aufnehmen.
4. AD-Replikation abwarten.
Installation:
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
```
### 8.3 regulaeres Dienstkonto
```powershell ```powershell
.\Install-BizTalkCheckmkPulse.ps1 ` .\Install-BizTalkCheckmkPulse.ps1 `
@@ -333,6 +348,19 @@ Installation:
Der Installer muss als lokaler Administrator laufen. Er vergibt keine Der Installer muss als lokaler Administrator laufen. Er vergibt keine
AD-/BizTalk-/SQL-Rechte; diese bleiben getrennte administrative Freigaben. AD-/BizTalk-/SQL-Rechte; diese bleiben getrennte administrative Freigaben.
Der installierte Task selbst laeuft mit `RunLevel Limited`. Der Installer fragt
das Kennwort mit `Get-Credential` ab; es wird nicht in Config oder Log
geschrieben. Das Konto benoetigt `Log on as a batch job`.
### 8.3 Optionales gMSA
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
```
### 8.4 Checkmk ### 8.4 Checkmk
Der installierte Wrapper liegt unter: Der installierte Wrapper liegt unter:
@@ -350,7 +378,8 @@ Snapshot nicht zusaetzlich verzoegert.
Nach dem Agent-Dump: Nach dem Agent-Dump:
1. Service Discovery fuer den BizTalk-Host ausfuehren. 1. Service Discovery fuer den BizTalk-Host ausfuehren.
2. sechs stabile Services aufnehmen. 2. acht stabile Services aufnehmen; den alten Service
`BizTalk Runtime Artifacts` nach erfolgreicher Discovery entfernen.
3. Changes aktivieren. 3. Changes aktivieren.
4. Views/Benachrichtigungen nach Umgebung konfigurieren. 4. Views/Benachrichtigungen nach Umgebung konfigurieren.
@@ -368,6 +397,12 @@ einzeilige Nachricht. Provider-Erfolge werden pro Lauf geloggt; der Consumer
loggt nur abgelehnte Snapshots. Exceptions werden mit Typ, Nachricht, loggt nur abgelehnte Snapshots. Exceptions werden mit Typ, Nachricht,
HRESULT-/Providerdetails und Stacktrace einzeilig gespeichert. HRESULT-/Providerdetails und Stacktrace einzeilig gespeichert.
Der erfolgreiche Abschluss nennt Suspensions, Routing Failure Reports,
Receive-Location-/Send-Port-Zahlen und Laufzeit. Jede strukturierte
WMI-/SQL-/Event-Log-Diagnose wird zusaetzlich als eigene `WARN`-Zeile
protokolliert und bleibt damit auch ausserhalb der gekuerzten Checkmk-Summary
vollstaendig nachvollziehbar.
Logging ist best effort: Ein blockiertes Log darf Checkmk-Ausgabe oder Logging ist best effort: Ein blockiertes Log darf Checkmk-Ausgabe oder
Snapshot-Publikation nicht zerstoeren. Der Provider entfernt beim Start Dateien Snapshot-Publikation nicht zerstoeren. Der Provider entfernt beim Start Dateien
aelter als `LogRetentionDays`. aelter als `LogRetentionDays`.
@@ -391,7 +426,7 @@ Pruefen:
1. Task existiert und ist aktiviert. 1. Task existiert und ist aktiviert.
2. `LastTaskResult` und Provider-Log. 2. `LastTaskResult` und Provider-Log.
3. Dienstkonto kann sich als Batch anmelden. 3. Dienstkonto kann sich als Batch anmelden.
4. gMSA ist lokal installiert und abrufbar. 4. gespeichertes Task-Kennwort ist nach Rotation/Ablauf noch gueltig.
5. Provider besitzt Modify auf `data` und `logs`. 5. Provider besitzt Modify auf `data` und `logs`.
6. EXE/Config sind ausfuehrbar. 6. EXE/Config sind ausfuehrbar.
@@ -468,7 +503,13 @@ Alarmierung:
| --- | --- | | --- | --- |
| `WarnResumableThreshold` | `1` | | `WarnResumableThreshold` | `1` |
| `CritNonResumableThreshold` | `1` | | `CritNonResumableThreshold` | `1` |
| `AlertOnArtifactRuntimeIssues` | `false` | | `CritRoutingFailureThreshold` | `1` |
| `AlertOnArtifactRuntimeIssues` | `true` |
| `ExpectedDisabledReceiveLocations` | leer; exakte Pipe-Liste |
| `ExpectedInactiveSendPorts` | leer; exakte Pipe-Liste |
| `AlertOnInactiveOrchestrations` | `false` |
| `MaxSummaryItems` | `5` |
| `MaxDetailCharacters` | `1600` |
| `EmitPerApplicationSuspensionServices` | `false` | | `EmitPerApplicationSuspensionServices` | `false` |
| `EventLogWarnThreshold` | `1` | | `EventLogWarnThreshold` | `1` |
| `EventLogCritThreshold` | `10` | | `EventLogCritThreshold` | `10` |
@@ -478,16 +519,20 @@ Alarmierung:
Automatisiert: Automatisiert:
- Release-Build .NET Framework 4.7.2, - Release-Build .NET Framework 4.7.2,
- exakt sechs Self-Test-Services, - exakt acht Self-Test-Services,
- unbekannte Quellen werden `UNKNOWN`, - unbekannte Quellen werden `UNKNOWN`,
- dynamische Anwendungsservices nur bei bekannter Anwendung, - dynamische Anwendungsservices nur bei bekannter Anwendung,
- WMI-Queryvertrag nutzt dokumentierte Properties, - WMI-Queryvertrag nutzt dokumentierte Properties,
- eingebetteter SQL-Loginfehler wird `Permission`, - eingebetteter SQL-Loginfehler wird `Permission`,
- abgewiesener Principal wird extrahiert, - abgewiesener Principal wird extrahiert,
- Routing Failure Reports werden separat und als non-resumable gezaehlt,
- Receive-/Send-Allowlisten trennen bewusste von unerwarteten Aus-Zustaenden,
- ein fehlender `IsDisabled`-Wert wird `UNKNOWN` statt still als enabled,
- betroffene Namen und Gesamtlaenge der Summary bleiben begrenzt,
- Snapshot-Roundtrip und Ersatz, - Snapshot-Roundtrip und Ersatz,
- SHA-256-Manipulation wird verworfen, - SHA-256-Manipulation wird verworfen,
- Stale-Snapshot wird verworfen, - Stale-Snapshot wird verworfen,
- Consumer-Fallback enthaelt sechs `UNKNOWN`-Services. - Consumer-Fallback enthaelt acht `UNKNOWN`-Services.
Windows-/ACC-Abnahme: Windows-/ACC-Abnahme:
@@ -497,10 +542,16 @@ Windows-/ACC-Abnahme:
4. `BizTalk Platform` zeigt `read_only_group=`. 4. `BizTalk Platform` zeigt `read_only_group=`.
5. `BizTalk SQL Access`: `targets=2`, `available=2`. 5. `BizTalk SQL Access`: `targets=2`, `available=2`.
6. keine Permission-`UNKNOWN`s. 6. keine Permission-`UNKNOWN`s.
7. ACL-Test: `LocalSystem` kann Snapshot lesen, nicht schreiben. 7. Suspensionsmetriken mit der BizTalk Group Hub Page plausibilisieren,
8. Task deaktivieren: nach 180 Sekunden sechs stale-`UNKNOWN`s. einschliesslich Routing Failure Reports.
9. Task wieder aktivieren: naechster Snapshot stellt Echtzustand her. 8. Eine bewusst deaktivierte Receive Location und einen inaktiven Send Port
10. Agent-Dump und Checkmk Service Discovery erfolgreich. ueber die exakten Allowlisten als expected bestaetigen.
9. Einen Testnamen aus der Allowlist entfernen und den erwarteten CRIT mit
kurzem `affected=`-Detail pruefen.
10. ACL-Test: `LocalSystem` kann Snapshot lesen, nicht schreiben.
11. Task deaktivieren: nach 180 Sekunden acht stale-`UNKNOWN`s.
12. Task wieder aktivieren: naechster Snapshot stellt Echtzustand her.
13. Agent-Dump und Checkmk Service Discovery erfolgreich.
Erst nach ACC-Abnahme erfolgt der gestufte Rollout nach DEV/TST/PRD. Erst nach ACC-Abnahme erfolgt der gestufte Rollout nach DEV/TST/PRD.
@@ -537,6 +588,10 @@ ZIP und Base64 werden erst nach dem Git-Commit aus genau diesem Commit erzeugt.
- https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server - https://learn.microsoft.com/en-us/biztalk/core/windows-groups-and-user-accounts-in-biztalk-server
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-biztalkreadonlyusergroup-property-wmi - https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-biztalkreadonlyusergroup-property-wmi
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi - https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-groupsetting-wmi
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-serviceinstance-serviceclass-property-wmi
- https://learn.microsoft.com/en-us/biztalk/core/types-of-message-failures
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-receivelocation-isdisabled-property-wmi
- https://learn.microsoft.com/en-us/biztalk/core/technical-reference/msbts-sendport-status-property-wmi
- https://docs.checkmk.com/latest/en/agent_windows.html - https://docs.checkmk.com/latest/en/agent_windows.html
- https://docs.checkmk.com/latest/en/localchecks.html - https://docs.checkmk.com/latest/en/localchecks.html
- https://docs.checkmk.com/latest/en/spool_directory.html - https://docs.checkmk.com/latest/en/spool_directory.html
+44 -34
View File
@@ -5,7 +5,7 @@ BizTalk Server 2020 auf Windows Server 2019. Die Anwendung trennt den
berechtigten BizTalk-Datenzugriff vollstaendig vom Checkmk-Agenten: berechtigten BizTalk-Datenzugriff vollstaendig vom Checkmk-Agenten:
```text ```text
Scheduled Task (dediziertes Dienstkonto oder gMSA) Scheduled Task (normales dediziertes Servicekonto)
| |
| jede Minute: BizTalkCheckmkPulse.exe --collect | jede Minute: BizTalkCheckmkPulse.exe --collect
v v
@@ -20,7 +20,7 @@ lokales BizTalk-WMI + BizTalk-SQL + Application Event Log
Checkmk Windows Agent (LocalSystem) Checkmk Windows Agent (LocalSystem)
| |
v v
sechs stabile Checkmk Local Checks acht kompakte Checkmk Local Checks
``` ```
Damit bekommt `LocalSystem` keine BizTalk- oder SQL-Berechtigung. Nur das Damit bekommt `LocalSystem` keine BizTalk- oder SQL-Berechtigung. Nur das
@@ -62,7 +62,7 @@ Der Datenaustausch ist bewusst defensiv:
Checkmk-Zeilen, korrekte SHA-256-Pruefsumme und ein maximales Alter von Checkmk-Zeilen, korrekte SHA-256-Pruefsumme und ein maximales Alter von
standardmaessig 180 Sekunden. standardmaessig 180 Sekunden.
- Fehlende, veraltete, abgeschnittene, manipulierte oder unlesbare Dateien - Fehlende, veraltete, abgeschnittene, manipulierte oder unlesbare Dateien
ergeben sechs gueltige `UNKNOWN`-Services statt einer kaputten Agent-Ausgabe. ergeben acht gueltige `UNKNOWN`-Services statt einer kaputten Agent-Ausgabe.
- Ein exklusives Lock und die Task-Einstellung `IgnoreNew` verhindern - Ein exklusives Lock und die Task-Einstellung `IgnoreNew` verhindern
ueberlappende Providerlaeufe. ueberlappende Providerlaeufe.
- Ein unerwarteter Providerfehler erzeugt nach Moeglichkeit einen aktuellen - Ein unerwarteter Providerfehler erzeugt nach Moeglichkeit einen aktuellen
@@ -80,9 +80,23 @@ Standardmaessig entstehen:
- `BizTalk SQL Access` - `BizTalk SQL Access`
- `BizTalk Suspended Instances` - `BizTalk Suspended Instances`
- `BizTalk Host Instances` - `BizTalk Host Instances`
- `BizTalk Runtime Artifacts` - `BizTalk Receive Locations`
- `BizTalk Send Ports`
- `BizTalk Orchestrations`
- `BizTalk Event Log` - `BizTalk Event Log`
Die drei Artefaktbereiche sind absichtlich getrennte Services. Dadurch sind
Zustand und Graphen direkt erkennbar, ohne eine lange Sammelzeile zu lesen:
- Suspensions: total, resumable, non-resumable und Routing Failure Reports
- Receive Locations: total, enabled, unerwartet/bewusst disabled und unbekannt
- Send Ports: total, started, stopped, bound, unbekannt sowie bewusst inactive
- Orchestrations: total, started, stopped, bound, unbound und unbekannt
Pro Service werden standardmaessig maximal fuenf betroffene Namen gezeigt.
Weitere Treffer erscheinen nur als `(+n more)`; Details sind zusaetzlich auf
1600 Zeichen begrenzt. Metriken bleiben trotzdem vollstaendig.
Mit `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` wird die Umgebung in den Mit `EnvironmentName=ACC`, `DEV`, `TST` oder `PRD` wird die Umgebung in den
Servicenamen aufgenommen, zum Beispiel `BizTalk ACC Platform`. Servicenamen aufgenommen, zum Beispiel `BizTalk ACC Platform`.
@@ -106,7 +120,7 @@ BizTalk-Server:
- .NET Framework 4.7.2 - .NET Framework 4.7.2
- Checkmk Windows Agent - Checkmk Windows Agent
- administrativer Zugriff fuer die einmalige Installation - administrativer Zugriff fuer die einmalige Installation
- dediziertes AD-Dienstkonto oder bevorzugt gMSA fuer den Provider - normales dediziertes AD-Servicekonto fuer den Provider
Das Provider-Konto benoetigt: Das Provider-Konto benoetigt:
@@ -116,9 +130,10 @@ Das Provider-Konto benoetigt:
- Mitgliedschaft in der exakt konfigurierten BizTalk Server Read Only - Mitgliedschaft in der exakt konfigurierten BizTalk Server Read Only
Users-Gruppe Users-Gruppe
Es soll weder lokaler Administrator noch SQL-`sysadmin` sein. Fuer ein gMSA Es soll weder lokaler Administrator noch SQL-`sysadmin` sein. Der Scheduled
muss der BizTalk-Server das verwaltete Kennwort abrufen duerfen und das Konto Task laeuft mit `RunLevel Limited`. Das Servicekonto braucht ein gespeichertes
lokal installiert sein. Task-Kennwort und das Recht `Log on as a batch job`. Ein gMSA bleibt optional,
ist aber fuer diese Installation nicht vorausgesetzt.
## Build und Tests ## Build und Tests
@@ -146,7 +161,7 @@ Format-Self-Test ohne WMI, SQL oder Event Log:
artifacts\BizTalkCheckmkPulse-deploy\application\BizTalkCheckmkPulse.exe --self-test artifacts\BizTalkCheckmkPulse-deploy\application\BizTalkCheckmkPulse.exe --self-test
``` ```
Erwartet werden exakt sechs `OK`-Zeilen. Die Regressionstests pruefen Erwartet werden exakt acht `OK`-Zeilen. Die Regressionstests pruefen
zusaetzlich Snapshot-Roundtrip, atomaren Ersatz, SHA-256-Manipulation, zusaetzlich Snapshot-Roundtrip, atomaren Ersatz, SHA-256-Manipulation,
Stale-Erkennung, stabile Fallbacks und die bestehenden BizTalk-WMI-Diagnosen. Stale-Erkennung, stabile Fallbacks und die bestehenden BizTalk-WMI-Diagnosen.
Ein Mono-Build ist eine hilfreiche Quellcodepruefung, ersetzt aber nicht die Ein Mono-Build ist eine hilfreiche Quellcodepruefung, ersetzt aber nicht die
@@ -167,9 +182,8 @@ Get-CimInstance `
``` ```
Ein AD-Administrator nimmt das neue Provider-Konto in Ein AD-Administrator nimmt das neue Provider-Konto in
`BizTalkReadOnlyUserGroup` auf. Bei einem gMSA endet der Kontoname mit `$`. `BizTalkReadOnlyUserGroup` auf. Nach AD-Replikation muss das Servicekonto durch
Nach AD-Replikation muss ein regulaeres Dienstkonto einen neuen Anmeldetoken einen neuen Tasklauf einen neuen Anmeldetoken erhalten.
erhalten; bei gMSA wird der Task nach der Gruppenfreigabe neu gestartet.
Die BizTalk-Konfiguration muss die Domain-Gruppe bereits als Windows-Login und Die BizTalk-Konfiguration muss die Domain-Gruppe bereits als Windows-Login und
in `BizTalkMgmtDb`, `BizTalkMsgBoxDb`, `BizTalkDTADb`, in `BizTalkMgmtDb`, `BizTalkMsgBoxDb`, `BizTalkDTADb`,
@@ -178,25 +192,7 @@ in `BizTalkMgmtDb`, `BizTalkMsgBoxDb`, `BizTalkDTADb`,
SQL-Administration fuer die Gruppe repariert, nicht als Einzelberechtigung SQL-Administration fuer die Gruppe repariert, nicht als Einzelberechtigung
fuer das Provider-Konto. fuer das Provider-Konto.
## Installation mit gMSA ## Installation mit normalem Servicekonto
Deployment-Paket auf den BizTalk-Server kopieren. In administrativer Windows
PowerShell:
```powershell
Set-Location C:\Temp\BizTalkCheckmkPulse-deploy
# Optional, falls das gMSA noch nicht lokal installiert wurde:
Install-ADServiceAccount -Identity svc_biztalk_cmk
Test-ADServiceAccount -Identity svc_biztalk_cmk
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
```
## Installation mit regulaerem Dienstkonto
```powershell ```powershell
Set-Location C:\Temp\BizTalkCheckmkPulse-deploy Set-Location C:\Temp\BizTalkCheckmkPulse-deploy
@@ -209,6 +205,13 @@ Der Installer fragt das Kennwort ueber `Get-Credential` ab und speichert es
durch die Windows-Aufgabenplanung. Das Kennwort steht weder in der durch die Windows-Aufgabenplanung. Das Kennwort steht weder in der
Konfigurationsdatei noch in den Logs. Konfigurationsdatei noch in den Logs.
Wenn das Kennwort rotiert oder ablaeuft, muss es im Scheduled Task aktualisiert
werden. Bis dahin wird der Snapshot nach 180 Sekunden stale und Checkmk zeigt
alle acht Services als `UNKNOWN`.
Ein gMSA kann weiterhin optional mit `-Gmsa` installiert werden; die
produktive Standardbeschreibung geht vom normalen Servicekonto aus.
Der Installer: Der Installer:
1. kopiert EXE und Config nach 1. kopiert EXE und Config nach
@@ -265,7 +268,7 @@ Verbindlicher Test im echten `LocalSystem`-Kontext:
Select-String -Pattern "BizTalk|UNKNOWN|Snapshot|Permission" -Context 0,1 Select-String -Pattern "BizTalk|UNKNOWN|Snapshot|Permission" -Context 0,1
``` ```
Danach in Checkmk eine Service Discovery ausfuehren, die sechs Services Danach in Checkmk eine Service Discovery ausfuehren, die acht Services
aufnehmen und Changes aktivieren. Ein zusaetzlicher Checkmk-Async-Cache ist aufnehmen und Changes aktivieren. Ein zusaetzlicher Checkmk-Async-Cache ist
nicht erforderlich: Der Consumer liest nur eine kleine lokale Datei und der nicht erforderlich: Der Consumer liest nur eine kleine lokale Datei und der
Provider besitzt bereits seinen eigenen Minutentakt. Provider besitzt bereits seinen eigenen Minutentakt.
@@ -322,7 +325,13 @@ Wichtige Werte:
| `SqlConnectionTimeoutSeconds` | `5` | SQL-Timeout je Ziel. | | `SqlConnectionTimeoutSeconds` | `5` | SQL-Timeout je Ziel. |
| `WarnResumableThreshold` | `1` | WARN ab n resumable Suspensions. | | `WarnResumableThreshold` | `1` | WARN ab n resumable Suspensions. |
| `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable Suspensions. | | `CritNonResumableThreshold` | `1` | CRIT ab n non-resumable Suspensions. |
| `AlertOnArtifactRuntimeIssues` | `false` | WARN fuer bewusst inaktive Artefakte aktivieren. | | `CritRoutingFailureThreshold` | `1` | CRIT ab n Routing Failure Reports. |
| `AlertOnArtifactRuntimeIssues` | `true` | Unerwartet deaktivierte Receive Locations bzw. inaktive Send Ports werden CRIT. |
| `ExpectedDisabledReceiveLocations` | leer | Pipe-getrennte exakte Allowlist: `Name` oder `Anwendung\Name`. |
| `ExpectedInactiveSendPorts` | leer | Pipe-getrennte exakte Allowlist: `Name` oder `Anwendung\Name`. |
| `AlertOnInactiveOrchestrations` | `false` | Optional WARN fuer stopped/bound/unbound Orchestrations. |
| `MaxSummaryItems` | `5` | Maximal angezeigte betroffene Artefakte je Service. |
| `MaxDetailCharacters` | `1600` | Harte Obergrenze fuer Checkmk-Summary. |
| `EmitPerApplicationSuspensionServices` | `false` | Zusaetzliche Anwendungsservices. | | `EmitPerApplicationSuspensionServices` | `false` | Zusaetzliche Anwendungsservices. |
| `EventLogLookbackMinutes` | `60` | Event-Log-Zeitfenster des Providers. | | `EventLogLookbackMinutes` | `60` | Event-Log-Zeitfenster des Providers. |
@@ -333,10 +342,11 @@ liest den naechsten atomar publizierten Snapshot.
| Beobachtung | Ursache / Massnahme | | Beobachtung | Ursache / Massnahme |
| --- | --- | | --- | --- |
| Alle sechs Services melden fehlenden Snapshot | Task, Provider-Log, Task-Konto und ACL pruefen. | | Alle acht Services melden fehlenden Snapshot | Task, Provider-Log, Task-Konto/Kennwort und ACL pruefen. |
| Snapshot ist `stale` | `LastTaskResult`, Laufzeit, WMI-/SQL-Timeout und Log pruefen. | | Snapshot ist `stale` | `LastTaskResult`, Laufzeit, WMI-/SQL-Timeout und Log pruefen. |
| SHA-256 oder Format ungueltig | Datei nicht manuell bearbeiten; Datentraeger/AV und Schreibpfad pruefen, Task neu starten. | | SHA-256 oder Format ungueltig | Datei nicht manuell bearbeiten; Datentraeger/AV und Schreibpfad pruefen, Task neu starten. |
| Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` pruefen. | | Provider meldet `Login failed` | Provider-Konto und exakt konfigurierte Read-Only-Gruppe sowie `BTS_READONLY_USERS` pruefen. |
| Receive Locations / Send Ports sind CRIT | `affected=` pruefen; nur fachlich bewusst inaktive Namen exakt in die jeweilige Allowlist aufnehmen. |
| `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema pruefen; keine Rechte ausweiten. | | `Wmi/Schema` | Klasse/Properties gegen BizTalk-2020-Schema pruefen; keine Rechte ausweiten. |
| Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos pruefen. | | Nur Event Log `UNKNOWN` | lokalen Application-Log-Zugriff des Provider-Kontos pruefen. |
| Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL pruefen. | | Task-Result `2` | Parallelstart oder Snapshot-I/O; Log und Lock/ACL pruefen. |
+4 -4
View File
@@ -128,7 +128,7 @@ if ($Gmsa) {
$principal = New-ScheduledTaskPrincipal ` $principal = New-ScheduledTaskPrincipal `
-UserId $CollectorAccount ` -UserId $CollectorAccount `
-LogonType Password ` -LogonType Password `
-RunLevel Highest -RunLevel Limited
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
} }
@@ -143,7 +143,7 @@ else {
$principal = New-ScheduledTaskPrincipal ` $principal = New-ScheduledTaskPrincipal `
-UserId $CollectorAccount ` -UserId $CollectorAccount `
-LogonType Password ` -LogonType Password `
-RunLevel Highest -RunLevel Limited
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Register-ScheduledTask ` Register-ScheduledTask `
-TaskName $taskName ` -TaskName $taskName `
@@ -155,8 +155,8 @@ else {
Write-Host "Fuehre formatseitigen Self-Test aus ..." Write-Host "Fuehre formatseitigen Self-Test aus ..."
$selfTest = & $targetExe --self-test $selfTest = & $targetExe --self-test
if ($LASTEXITCODE -ne 0 -or @($selfTest).Count -ne 6) { if ($LASTEXITCODE -ne 0 -or @($selfTest).Count -ne 8) {
throw "Self-Test fehlgeschlagen. Erwartet wurden sechs Checkmk-Zeilen." throw "Self-Test fehlgeschlagen. Erwartet wurden acht Checkmk-Zeilen."
} }
Write-Host "Starte den Provider einmalig ..." Write-Host "Starte den Provider einmalig ..."
+6 -2
View File
@@ -9,7 +9,9 @@ if not exist "%EXE%" (
echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Runtime Artifacts" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk Receive Locations" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Send Ports" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Orchestrations" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
echo 3 "BizTalk Event Log" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen. echo 3 "BizTalk Event Log" - BizTalkCheckmkPulse.exe fehlt unter %BASE%. Massnahme: Deployment-Paket und Dateirechte pruefen.
exit /b 0 exit /b 0
) )
@@ -21,7 +23,9 @@ if not "%EXITCODE%"=="0" (
echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, EXE/Config-Dateien und Windows Application Log pruefen. echo 3 "BizTalk SQL Access" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: .NET Framework 4.7.2, EXE/Config-Dateien und Windows Application Log pruefen.
echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen. echo 3 "BizTalk Suspended Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen. echo 3 "BizTalk Host Instances" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
echo 3 "BizTalk Runtime Artifacts" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen. echo 3 "BizTalk Receive Locations" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Provider-Log pruefen.
echo 3 "BizTalk Send Ports" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Provider-Log pruefen.
echo 3 "BizTalk Orchestrations" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Provider-Log pruefen.
echo 3 "BizTalk Event Log" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen. echo 3 "BizTalk Event Log" - BizTalkCheckmkPulse.exe endete mit Exitcode=%EXITCODE%. Massnahme: Programmstart und Windows Application Log pruefen.
) )
exit /b 0 exit /b 0
+4 -9
View File
@@ -18,7 +18,7 @@ NEUE VERBINDLICHE ARCHITEKTUR
Das Maschinenkonto erhaelt keine BizTalk-Berechtigung. Das Maschinenkonto erhaelt keine BizTalk-Berechtigung.
1. Ein dediziertes Dienstkonto oder bevorzugt gMSA laeuft minuetlich als 1. Ein normales dediziertes Servicekonto laeuft minuetlich als
Scheduled Task "BizTalk Checkmk Pulse Provider". Scheduled Task "BizTalk Checkmk Pulse Provider".
2. Dieses Konto fuehrt BizTalkCheckmkPulse.exe --collect aus. 2. Dieses Konto fuehrt BizTalkCheckmkPulse.exe --collect aus.
3. Es schreibt atomar einen SHA-256-geschuetzten Snapshot nach: 3. Es schreibt atomar einen SHA-256-geschuetzten Snapshot nach:
@@ -64,14 +64,7 @@ NICHT DURCHFUEHREN
INSTALLATION INSTALLATION
============ ============
gMSA: Normales Servicekonto:
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
Regulaeres Dienstkonto:
.\Install-BizTalkCheckmkPulse.ps1 ` .\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk' ` -CollectorAccount 'BEW\svc_biztalk_cmk' `
@@ -102,6 +95,8 @@ ERWARTET
- Provider-Log nennt das dedizierte Konto - Provider-Log nennt das dedizierte Konto
- BizTalk Platform zeigt read_only_group=<exakte Gruppe> - BizTalk Platform zeigt read_only_group=<exakte Gruppe>
- BizTalk SQL Access zeigt targets=2 und available=2 - BizTalk SQL Access zeigt targets=2 und available=2
- Suspensionsmetriken enthalten resumable, non-resumable und Routing Failure Reports
- Receive Locations und Send Ports trennen expected von unexpected inactive
- keine berechtigungsbedingten UNKNOWN-Services - keine berechtigungsbedingten UNKNOWN-Services
- LocalSystem besitzt auf data nur Lesezugriff - LocalSystem besitzt auf data nur Lesezugriff
+64 -14
View File
@@ -11,7 +11,7 @@ Die Ausgabe ist so gebaut, dass sie auch mit Checkmk 2.4.0p4 kompatibel bleibt:
Die fachlichen Daten werden minuetlich vom privilegierten Scheduled-Task- Die fachlichen Daten werden minuetlich vom privilegierten Scheduled-Task-
Provider erzeugt. Der Checkmk-Agent liest als `LocalSystem` nur den Provider erzeugt. Der Checkmk-Agent liest als `LocalSystem` nur den
integritaets- und altersgeprueften Snapshot. Ist dieser Transport nicht integritaets- und altersgeprueften Snapshot. Ist dieser Transport nicht
verfuegbar, liefern alle sechs stabilen Services `UNKNOWN`. verfuegbar, liefern alle acht stabilen Services `UNKNOWN`.
## BizTalk Platform ## BizTalk Platform
@@ -77,6 +77,7 @@ Zweck:
- Ermittelt suspendierte BizTalk-Service-Instanzen. - Ermittelt suspendierte BizTalk-Service-Instanzen.
- Unterscheidet resumable und non-resumable. - Unterscheidet resumable und non-resumable.
- Zaehlt Routing Failure Reports (`ServiceClass=64`) separat.
- Zeigt betroffene Anwendungen und Beispiele in der Summary. - Zeigt betroffene Anwendungen und Beispiele in der Summary.
Status: Status:
@@ -86,6 +87,7 @@ Status:
| `OK` | keine suspendierten Instanzen. | | `OK` | keine suspendierten Instanzen. |
| `WARN` | `biztalk_suspended_resumable >= WarnResumableThreshold`. | | `WARN` | `biztalk_suspended_resumable >= WarnResumableThreshold`. |
| `CRIT` | `biztalk_suspended_nonresumable >= CritNonResumableThreshold`. | | `CRIT` | `biztalk_suspended_nonresumable >= CritNonResumableThreshold`. |
| `CRIT` | `biztalk_routing_failure_reports >= CritRoutingFailureThreshold`. |
| `UNKNOWN` | `MSBTS_ServiceInstance` nicht verlaesslich lesbar oder Programmfehler. | | `UNKNOWN` | `MSBTS_ServiceInstance` nicht verlaesslich lesbar oder Programmfehler. |
Metriken: Metriken:
@@ -95,6 +97,7 @@ Metriken:
| `biztalk_suspended_total` | Summe aller suspendierten Instanzen. | | `biztalk_suspended_total` | Summe aller suspendierten Instanzen. |
| `biztalk_suspended_resumable` | Resumable suspended instances. | | `biztalk_suspended_resumable` | Resumable suspended instances. |
| `biztalk_suspended_nonresumable` | Non-resumable suspended instances. | | `biztalk_suspended_nonresumable` | Non-resumable suspended instances. |
| `biztalk_routing_failure_reports` | Routing Failure Reports; Teilmenge der non-resumable Instanzen. |
## BizTalk Host Instances ## BizTalk Host Instances
@@ -123,38 +126,85 @@ Metriken:
| `biztalk_host_instances_pending` | StartPending/StopPending. | | `biztalk_host_instances_pending` | StartPending/StopPending. |
| `biztalk_host_instances_unknown` | Nicht interpretierbarer Status. | | `biztalk_host_instances_unknown` | Nicht interpretierbarer Status. |
## BizTalk Runtime Artifacts ## BizTalk Receive Locations
Zweck: Zweck:
- Summiert Laufzeitzustaende von Receive Locations, Send Ports und Orchestrations. - Zaehlt alle konfigurierten Receive Locations und ihren `IsDisabled`-Zustand.
- Liefert dashboard-taugliche Zahlen zur Laufzeitlandschaft. - Trennt bewusst deaktivierte von unerwartet deaktivierten Namen.
Status: Status:
| Status | Bedingung | | Status | Bedingung |
| --- | --- | | --- | --- |
| `OK` | Artefakte lesbar; keine unbekannten Statuswerte. | | `OK` | Daten lesbar und keine unerwartet deaktivierte Receive Location. |
| `WARN` | Nur bei `AlertOnArtifactRuntimeIssues=true` und inaktiven/deaktivierten Artefakten. | | `CRIT` | Mindestens eine unerwartet deaktivierte Receive Location bei `AlertOnArtifactRuntimeIssues=true`. |
| `CRIT` | Unbekannte Send-Port- oder Orchestration-Statuswerte. | | `UNKNOWN` | `MSBTS_ReceiveLocation` nicht verlaesslich lesbar oder `IsDisabled` eines Objekts nicht interpretierbar. |
| `UNKNOWN` | Mindestens eine erforderliche Artefaktklasse nicht lesbar oder Programmfehler. |
Metriken: Metriken:
| Metrik | Bedeutung | | Metrik | Bedeutung |
| --- | --- | | --- | --- |
| `biztalk_applications` | Anzahl der per WMI erkannten Anwendungs-Buckets; `(unknown)` ist moeglich, da die dokumentierten Artefaktklassen keine verlaessliche Application-Property garantieren. | | `biztalk_receive_locations_total` | Gesamtzahl Receive Locations. |
| `biztalk_receive_locations` | Gesamtzahl Receive Locations. | | `biztalk_receive_locations_enabled` | Aktivierte Receive Locations. |
| `biztalk_receive_locations_disabled` | Deaktivierte Receive Locations. | | `biztalk_receive_locations_disabled` | Deaktivierte Receive Locations. |
| `biztalk_send_ports` | Gesamtzahl Send Ports. | | `biztalk_receive_locations_unexpected_disabled` | Deaktiviert und nicht in der Allowlist; CRIT ab 1. |
| `biztalk_receive_locations_expected_disabled` | Bewusst deaktiviert und exakt allowlisted. |
| `biztalk_receive_locations_unknown` | Fehlender oder nicht interpretierbarer `IsDisabled`-Wert. |
## BizTalk Send Ports
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | Alle nicht allowlisted Send Ports sind started. |
| `CRIT` | Mindestens ein unerwartet stopped/bound Port oder unbekannter Status. |
| `UNKNOWN` | `MSBTS_SendPort` nicht verlaesslich lesbar. |
Metriken:
| Metrik | Bedeutung |
| --- | --- |
| `biztalk_send_ports_total` | Gesamtzahl Send Ports. |
| `biztalk_send_ports_started` | Gestartete Send Ports. | | `biztalk_send_ports_started` | Gestartete Send Ports. |
| `biztalk_send_ports_inactive` | Bound oder stopped Send Ports. | | `biztalk_send_ports_stopped` | Enlisted, Subscription deaktiviert (`Status=2`). |
| `biztalk_send_ports_bound` | Bound/unenlisted (`Status=1`). |
| `biztalk_send_ports_unexpected_inactive` | Stopped/bound und nicht allowlisted; CRIT ab 1. |
| `biztalk_send_ports_expected_inactive` | Bewusst inactive und exakt allowlisted. |
| `biztalk_send_ports_unknown` | Nicht interpretierbarer Send-Port-Status. | | `biztalk_send_ports_unknown` | Nicht interpretierbarer Send-Port-Status. |
| `biztalk_orchestrations` | Gesamtzahl Orchestrations. |
## BizTalk Orchestrations
Status:
| Status | Bedingung |
| --- | --- |
| `OK` | Daten lesbar und kein unbekannter Status; inaktive Orchestrations sind standardmaessig nur Information. |
| `WARN` | stopped/bound/unbound bei `AlertOnInactiveOrchestrations=true`. |
| `CRIT` | Mindestens ein unbekannter Statuswert. |
| `UNKNOWN` | `MSBTS_Orchestration` nicht verlaesslich lesbar. |
Metriken:
| Metrik | Bedeutung |
| --- | --- |
| `biztalk_orchestrations_total` | Gesamtzahl Orchestrations. |
| `biztalk_orchestrations_started` | Gestartete Orchestrations. | | `biztalk_orchestrations_started` | Gestartete Orchestrations. |
| `biztalk_orchestrations_inactive` | Bound, unbound oder stopped Orchestrations. | | `biztalk_orchestrations_stopped` | Gestoppte Orchestrations. |
| `biztalk_orchestrations_bound` | Bound Orchestrations. |
| `biztalk_orchestrations_unbound` | Unbound Orchestrations. |
| `biztalk_orchestrations_unknown` | Nicht interpretierbarer Orchestration-Status. | | `biztalk_orchestrations_unknown` | Nicht interpretierbarer Orchestration-Status. |
Allowlist-Syntax fuer Receive Locations und Send Ports:
```text
Name|Anwendung\Name|Weiterer Name
```
Die Auswertung ist case-insensitive und exakt. Wildcards werden bewusst nicht
unterstuetzt, damit eine breite Regel keine neuen Artefaktfehler verdeckt.
## BizTalk Event Log ## BizTalk Event Log
Zweck: Zweck:
+33 -21
View File
@@ -7,54 +7,66 @@
0 "BizTalk SQL Access" biztalk_sql_targets_total=0;;;0 Self test OK. No SQL connection was opened. 0 "BizTalk SQL Access" biztalk_sql_targets_total=0;;;0 Self test OK. No SQL connection was opened.
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0 Self test OK. 0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0 Self test OK.
0 "BizTalk Host Instances" biztalk_host_instances_total=0;;;0 Self test OK. No WMI query was executed. 0 "BizTalk Host Instances" biztalk_host_instances_total=0;;;0 Self test OK. No WMI query was executed.
0 "BizTalk Runtime Artifacts" biztalk_applications=0;;;0 Self test OK. No WMI query was executed. 0 "BizTalk Receive Locations" biztalk_receive_locations_total=0;;;0 Self test OK. No WMI query was executed.
0 "BizTalk Send Ports" biztalk_send_ports_total=0;;;0 Self test OK. No WMI query was executed.
0 "BizTalk Orchestrations" biztalk_orchestrations_total=0;;;0 Self test OK. No WMI query was executed.
0 "BizTalk Event Log" biztalk_eventlog_errors=0;;;0 Self test OK. No event log was read. 0 "BizTalk Event Log" biztalk_eventlog_errors=0;;;0 Self test OK. No event log was read.
``` ```
## Gesunder BizTalk-Server ## Gesundes kompaktes Servicebild
```text ```text
0 "BizTalk Platform" - BizTalk WMI and platform data reachable, server=BIZTALK01, group=BizTalkMgmtDb, read_only_group=CONTOSO\BizTalk Server Read Only Users, operator_group=CONTOSO\BizTalk Server Operators, mgmt_db=SQL01\BizTalkMgmtDb, msgbox_db=SQL01\BizTalkMsgBoxDb 0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0|biztalk_routing_failure_reports=0;;1;0 Suspended total=0, resumable=0, nonresumable=0, routing_failure_reports=0.
0 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=2;;;0|biztalk_sql_targets_failed=0;;1;0 SQL access targets=2, available=2, discovery_complete=True, execution_identity=CONTOSO\svc_biztalk_cmk$, network_identity=CONTOSO\svc_biztalk_cmk$; databases=Management=SQL01\BizTalkMgmtDb:OK, MasterMessageBox=SQL01\BizTalkMsgBoxDb:OK 0 "BizTalk Receive Locations" biztalk_receive_locations_total=24;;;0|biztalk_receive_locations_enabled=23;;;0|biztalk_receive_locations_disabled=1;;;0|biztalk_receive_locations_unexpected_disabled=0;;1;0|biztalk_receive_locations_expected_disabled=1;;;0|biztalk_receive_locations_unknown=0;;1;0 Receive locations total=24, enabled=23, unexpected_disabled=0, expected_disabled=1, unknown=0
0 "BizTalk Suspended Instances" biztalk_suspended_total=0;;;0|biztalk_suspended_resumable=0;1;;0|biztalk_suspended_nonresumable=0;;1;0 No suspended BizTalk service instances found. 0 "BizTalk Send Ports" biztalk_send_ports_total=31;;;0|biztalk_send_ports_started=30;;;0|biztalk_send_ports_stopped=1;;;0|biztalk_send_ports_bound=0;;;0|biztalk_send_ports_unexpected_inactive=0;;1;0|biztalk_send_ports_expected_inactive=1;;;0|biztalk_send_ports_unknown=0;;1;0 Send ports total=31, started=30, stopped=1, bound=0, unexpected_inactive=0, expected_inactive=1, unknown=0
0 "BizTalk Host Instances" biztalk_host_instances_total=4;;;0|biztalk_host_instances_started=4;;;0|biztalk_host_instances_stopped=0;;1;0|biztalk_host_instances_pending=0;1;;0|biztalk_host_instances_unknown=0;;1;0 Host instances total=4, started=4, stopped=0, pending=0, unknown=0 0 "BizTalk Orchestrations" biztalk_orchestrations_total=12;;;0|biztalk_orchestrations_started=11;;;0|biztalk_orchestrations_stopped=1;;;0|biztalk_orchestrations_bound=0;;;0|biztalk_orchestrations_unbound=0;;;0|biztalk_orchestrations_unknown=0;;1;0 Orchestrations total=12, started=11, stopped=1, bound=0, unbound=0, unknown=0; affected_apps=Maintenance(inactive=1,unknown=0)
0 "BizTalk Runtime Artifacts" biztalk_applications=8;;;0|biztalk_receive_locations=24;;;0|biztalk_receive_locations_disabled=0;1;;0|biztalk_send_ports=31;;;0|biztalk_send_ports_started=31;;;0|biztalk_send_ports_inactive=0;1;;0|biztalk_send_ports_unknown=0;;1;0|biztalk_orchestrations=12;;;0|biztalk_orchestrations_started=12;;;0|biztalk_orchestrations_inactive=0;1;;0|biztalk_orchestrations_unknown=0;;1;0 Applications=8; receive_locations total=24, disabled=0; send_ports total=31, started=31, inactive=0, unknown=0; orchestrations total=12, started=12, inactive=0, unknown=0
0 "BizTalk Event Log" biztalk_eventlog_errors=0;1;10;0|biztalk_eventlog_warnings=0;1;;0 BizTalk-related Application log entries in last 60 minutes: errors=0, warnings=0
``` ```
## Suspended Instances Die bewusst inaktiven Receive-/Send-Artefakte stehen in den exakten
Allowlisten. Sie bleiben als Metrik sichtbar, erzeugen aber keinen Alarm.
## Suspensions und Routing Failure Report
```text ```text
2 "BizTalk Suspended Instances" biztalk_suspended_total=3;;;0|biztalk_suspended_resumable=2;1;;0|biztalk_suspended_nonresumable=1;;1;0 3 suspended BizTalk service instance(s): resumable=2, nonresumable=1; applications=OrderProcessing(R=2, NR=1); examples=Send.Invoice on ProcessingHost 2 "BizTalk Suspended Instances" biztalk_suspended_total=3;;;0|biztalk_suspended_resumable=2;1;;0|biztalk_suspended_nonresumable=1;;1;0|biztalk_routing_failure_reports=1;;1;0 Suspended total=3, resumable=2, nonresumable=1, routing_failure_reports=1; applications=OrderProcessing(R=2,NR=0); recent=Receive.Order@ProcessingHost, Send.Invoice@ProcessingHost
``` ```
## SQL-Berechtigungsfehler Routing Failure Reports sind eine Teilmenge der non-resumable Instanzen und
werden nicht doppelt zum Total addiert.
## Unerwartet deaktivierte Receive Locations
```text ```text
3 "BizTalk SQL Access" biztalk_sql_targets_total=2;;;0|biztalk_sql_targets_available=0;;;0|biztalk_sql_targets_failed=2;;1;0 SQL access targets=2, available=0, discovery_complete=True, execution_identity=CONTOSO\svc_biztalk_cmk$, network_identity=CONTOSO\svc_biztalk_cmk$; databases=Management=SQL01\BizTalkMgmtDb:FAILED, MasterMessageBox=SQL01\BizTalkMsgBoxDb:FAILED; diagnostics: Sql/Permission [Management SQL01\BizTalkMgmtDb]: SQL Server hat die integrierte Anmeldung abgelehnt. Massnahme: Provider-Konto und konfigurierte BizTalk-Read-Only-Gruppe mit BTS_READONLY_USERS pruefen; keine direkten BizTalk-DB-Rollen vergeben. Technik: SqlException Number=18456: Login failed for user 2 "BizTalk Receive Locations" biztalk_receive_locations_total=24;;;0|biztalk_receive_locations_enabled=22;;;0|biztalk_receive_locations_disabled=2;;;0|biztalk_receive_locations_unexpected_disabled=1;;1;0|biztalk_receive_locations_expected_disabled=1;;;0|biztalk_receive_locations_unknown=0;;1;0 Receive locations total=24, enabled=22, unexpected_disabled=1, expected_disabled=1, unknown=0; affected=Orders\RL_Order_In
``` ```
## WMI-Berechtigungsfehler ## Unerwartet inaktive Send Ports
```text ```text
3 "BizTalk Suspended Instances" - Suspendierte Instanzen konnten nicht verlaesslich gelesen werden. Ein leerer Datenbestand wird nicht als OK gewertet.; diagnostics: Wmi/Permission [MSBTS_ServiceInstance]: Zugriff des Provider-Kontos auf MSBTS_ServiceInstance wurde verweigert. Massnahme: Provider-Identitaet und konfigurierte BizTalk-Read-Only-Gruppe pruefen. Technik: UnauthorizedAccessException HRESULT=0x80070005: Access is denied 2 "BizTalk Send Ports" biztalk_send_ports_total=31;;;0|biztalk_send_ports_started=29;;;0|biztalk_send_ports_stopped=1;;;0|biztalk_send_ports_bound=1;;;0|biztalk_send_ports_unexpected_inactive=1;;1;0|biztalk_send_ports_expected_inactive=1;;;0|biztalk_send_ports_unknown=0;;1;0 Send ports total=31, started=29, stopped=1, bound=1, unexpected_inactive=1, expected_inactive=1, unknown=0; affected=Orders\SP_Order_Out(bound)
``` ```
## Begrenzte Detailausgabe
```text
2 "BizTalk Receive Locations" ... affected=App\RL1, App\RL2, App\RL3, App\RL4, App\RL5 (+17 more)
```
Die Zaehler und Performance-Daten enthalten weiterhin alle 22 betroffenen
Artefakte. Nur die Textliste ist begrenzt.
## Fehlender oder veralteter Provider-Snapshot ## Fehlender oder veralteter Provider-Snapshot
Alle acht Services werden `UNKNOWN`, zum Beispiel:
```text ```text
3 "BizTalk Platform" - Privilegierter BizTalk-Datensnapshot nicht verfuegbar: Snapshot is stale: age=241s, maximum=180s. Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen. 3 "BizTalk Platform" - Privilegierter BizTalk-Datensnapshot nicht verfuegbar: Snapshot is stale: age=241s, maximum=180s. Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen.
3 "BizTalk SQL Access" - Privilegierter BizTalk-Datensnapshot nicht verfuegbar: Snapshot is stale: age=241s, maximum=180s. Massnahme: Scheduled Task 'BizTalk Checkmk Pulse Provider', Provider-Log, Snapshot-Alter und ACL pruefen.
``` ```
## WMI-Schemafehler ## WMI-Schemafehler
```text Nur der fachlich betroffene Service wird `UNKNOWN`:
3 "BizTalk Runtime Artifacts" - BizTalk Runtime Artifacts konnten nicht vollstaendig gelesen werden. Teilwerte werden nicht als OK gewertet.; diagnostics: Wmi/Schema [MSBTS_Orchestration]: WMI-Klasse oder WQL fuer MSBTS_Orchestration wird vom installierten BizTalk-Provider nicht unterstuetzt. Massnahme: Klassen- und Property-Namen gegen das installierte BizTalk-WMI-Schema und die Microsoft-Dokumentation pruefen. Rechteerhoehung behebt InvalidClass/InvalidQuery nicht. Technik: ManagementException HRESULT=0x80041010: Invalid class WQL={SELECT * FROM MSBTS_Orchestration} DurationMs=18
```
## Fehlendes Deployment
```text ```text
3 "BizTalk Platform" - BizTalkCheckmkPulse.exe fehlt unter C:\Program Files\BizTalkCheckmkPulse. Massnahme: Install-BizTalkCheckmkPulse.ps1 erneut als Administrator ausfuehren und danach cmk-agent-ctl.exe dump erneut ausfuehren. 3 "BizTalk Orchestrations" - Orchestrations konnten nicht verlaesslich gelesen werden.; diagnostics: Wmi/Schema [MSBTS_Orchestration]: WMI-Klasse oder WQL wird nicht unterstuetzt. Massnahme: Klassen- und Property-Namen gegen das installierte BizTalk-WMI-Schema pruefen.
``` ```
+7 -13
View File
@@ -4,7 +4,7 @@
| Team | Aufgabe | | Team | Aufgabe |
| --- | --- | | --- | --- |
| AD/Security | dediziertes Dienstkonto oder gMSA; Aufnahme in exakt konfigurierte BizTalk-Read-Only-Gruppe | | AD/Security | normales dediziertes Servicekonto; Aufnahme in exakt konfigurierte BizTalk-Read-Only-Gruppe |
| BizTalk/SQL | Gruppenabbildung und `BTS_READONLY_USERS` bestaetigen; keine Einzelrechte | | BizTalk/SQL | Gruppenabbildung und `BTS_READONLY_USERS` bestaetigen; keine Einzelrechte |
| Windows | Paket installieren, ACL und Scheduled Task pruefen | | Windows | Paket installieren, ACL und Scheduled Task pruefen |
| Checkmk | Wrapper verteilen beziehungsweise Installation koordinieren, Discovery und Alarmierung | | Checkmk | Wrapper verteilen beziehungsweise Installation koordinieren, Discovery und Alarmierung |
@@ -15,16 +15,7 @@ Provider-Konto und publiziert einen validierbaren Snapshot.
## Installation ## Installation
gMSA: Normales Servicekonto:
```powershell
.\Install-BizTalkCheckmkPulse.ps1 `
-CollectorAccount 'BEW\svc_biztalk_cmk$' `
-Gmsa `
-EnvironmentName ACC
```
Regulaeres Dienstkonto:
```powershell ```powershell
.\Install-BizTalkCheckmkPulse.ps1 ` .\Install-BizTalkCheckmkPulse.ps1 `
@@ -70,7 +61,8 @@ Verbindlicher Agent-Dump:
2. `LastTaskResult=0`, frischen Snapshot und Log pruefen. 2. `LastTaskResult=0`, frischen Snapshot und Log pruefen.
3. Agent-Dump im `LocalSystem`-Kontext pruefen. 3. Agent-Dump im `LocalSystem`-Kontext pruefen.
4. Service Discovery fuer den BizTalk-Host ausfuehren. 4. Service Discovery fuer den BizTalk-Host ausfuehren.
5. sechs stabile Services aufnehmen. 5. acht stabile Services aufnehmen und den alten
`BizTalk Runtime Artifacts`-Service entfernen.
6. Changes aktivieren. 6. Changes aktivieren.
7. Views, Servicegruppen und Benachrichtigungen einrichten. 7. Views, Servicegruppen und Benachrichtigungen einrichten.
@@ -117,11 +109,13 @@ Soll:
- Log nennt das dedizierte Providerkonto. - Log nennt das dedizierte Providerkonto.
- Platform zeigt `read_only_group=...`. - Platform zeigt `read_only_group=...`.
- SQL Access zeigt `targets=2`, `available=2`. - SQL Access zeigt `targets=2`, `available=2`.
- Suspended Instances zeigt total/resumable/non-resumable/routing failures.
- Receive Locations und Send Ports zeigen getrennte expected/unexpected Werte.
- keine berechtigungsbedingten `UNKNOWN`-Services. - keine berechtigungsbedingten `UNKNOWN`-Services.
## Alarmierung der Transportkette ## Alarmierung der Transportkette
Ein Ausfall des Providers wird ueber alle sechs Services als `UNKNOWN` Ein Ausfall des Providers wird ueber alle acht Services als `UNKNOWN`
sichtbar. Die Summary nennt fehlenden, unlesbaren, ungueltigen oder stale sichtbar. Die Summary nennt fehlenden, unlesbaren, ungueltigen oder stale
Snapshot. Als Betriebsregel sollte `UNKNOWN` dieser Services genauso Snapshot. Als Betriebsregel sollte `UNKNOWN` dieser Services genauso
eskaliert werden wie ein technischer Monitoringausfall. eskaliert werden wie ein technischer Monitoringausfall.
+10 -3
View File
@@ -20,10 +20,17 @@
<add key="SqlConnectionTimeoutSeconds" value="5" /> <add key="SqlConnectionTimeoutSeconds" value="5" />
<add key="WarnResumableThreshold" value="1" /> <add key="WarnResumableThreshold" value="1" />
<add key="CritNonResumableThreshold" value="1" /> <add key="CritNonResumableThreshold" value="1" />
<add key="MaxSummaryItems" value="12" /> <add key="CritRoutingFailureThreshold" value="1" />
<add key="MaxSummaryItems" value="5" />
<add key="MaxDetailCharacters" value="1600" />
<!-- Gestoppte Artefakte koennen in BizTalk-Landschaften fachlich beabsichtigt sein. --> <!-- Unerwartet deaktivierte Receive Locations und inaktive Send Ports sind standardmaessig CRIT. -->
<add key="AlertOnArtifactRuntimeIssues" value="false" /> <add key="AlertOnArtifactRuntimeIssues" value="true" />
<!-- Exakte Namen, Pipe-getrennt: Name oder Anwendung\Name. Keine Wildcards. -->
<add key="ExpectedDisabledReceiveLocations" value="" />
<add key="ExpectedInactiveSendPorts" value="" />
<!-- Gestoppte/bound Orchestrations koennen fachlich beabsichtigt sein. -->
<add key="AlertOnInactiveOrchestrations" value="false" />
<add key="EmitPerApplicationSuspensionServices" value="false" /> <add key="EmitPerApplicationSuspensionServices" value="false" />
<add key="ProbeEventLog" value="true" /> <add key="ProbeEventLog" value="true" />
+247 -71
View File
@@ -19,6 +19,9 @@ namespace BizTalkCheckmkPulse
private const int HostPausePending = 6; private const int HostPausePending = 6;
private const int HostPaused = 7; private const int HostPaused = 7;
private const int HostUnknown = 8; private const int HostUnknown = 8;
private const int SendPortBound = 1;
private const int SendPortStopped = 2;
private const int SendPortStarted = 3;
private readonly MonitoringOptions _options; private readonly MonitoringOptions _options;
/// <summary> /// <summary>
@@ -41,7 +44,9 @@ namespace BizTalkCheckmkPulse
yield return FormatSqlAccess(result); yield return FormatSqlAccess(result);
yield return FormatSuspendedInstances(result); yield return FormatSuspendedInstances(result);
yield return FormatHostInstances(result); yield return FormatHostInstances(result);
yield return FormatRuntimeArtifacts(result); yield return FormatReceiveLocations(result);
yield return FormatSendPorts(result);
yield return FormatOrchestrations(result);
yield return FormatEventLog(result); yield return FormatEventLog(result);
if (_options.EmitPerApplicationSuspensionServices && result.Platform.SuspendedInstancesDataAvailable) if (_options.EmitPerApplicationSuspensionServices && result.Platform.SuspendedInstancesDataAvailable)
@@ -63,7 +68,9 @@ namespace BizTalkCheckmkPulse
yield return BuildLine(CheckState.Ok, _options.ServiceName("SQL Access"), "biztalk_sql_targets_total=0;;;0", "Self test OK. No SQL connection was opened."); yield return BuildLine(CheckState.Ok, _options.ServiceName("SQL Access"), "biztalk_sql_targets_total=0;;;0", "Self test OK. No SQL connection was opened.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Suspended Instances"), "biztalk_suspended_total=0;;;0", "Self test OK."); yield return BuildLine(CheckState.Ok, _options.ServiceName("Suspended Instances"), "biztalk_suspended_total=0;;;0", "Self test OK.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Host Instances"), "biztalk_host_instances_total=0;;;0", "Self test OK. No WMI query was executed."); yield return BuildLine(CheckState.Ok, _options.ServiceName("Host Instances"), "biztalk_host_instances_total=0;;;0", "Self test OK. No WMI query was executed.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Runtime Artifacts"), "biztalk_applications=0;;;0", "Self test OK. No WMI query was executed."); yield return BuildLine(CheckState.Ok, _options.ServiceName("Receive Locations"), "biztalk_receive_locations_total=0;;;0", "Self test OK. No WMI query was executed.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Send Ports"), "biztalk_send_ports_total=0;;;0", "Self test OK. No WMI query was executed.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Orchestrations"), "biztalk_orchestrations_total=0;;;0", "Self test OK. No WMI query was executed.");
yield return BuildLine(CheckState.Ok, _options.ServiceName("Event Log"), "biztalk_eventlog_errors=0;;;0", "Self test OK. No event log was read."); yield return BuildLine(CheckState.Ok, _options.ServiceName("Event Log"), "biztalk_eventlog_errors=0;;;0", "Self test OK. No event log was read.");
} }
@@ -79,7 +86,9 @@ namespace BizTalkCheckmkPulse
yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", action); yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", action);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", action); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", action);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", action); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", action);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Runtime Artifacts"), "-", action); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Receive Locations"), "-", action);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Send Ports"), "-", action);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Orchestrations"), "-", action);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", action); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", action);
} }
@@ -95,7 +104,9 @@ namespace BizTalkCheckmkPulse
yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", detail); yield return BuildLine(CheckState.Unknown, _options.ServiceName("SQL Access"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", detail); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Suspended Instances"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", detail); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Host Instances"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Runtime Artifacts"), "-", detail); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Receive Locations"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Send Ports"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Orchestrations"), "-", detail);
yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", detail); yield return BuildLine(CheckState.Unknown, _options.ServiceName("Event Log"), "-", detail);
} }
@@ -182,19 +193,22 @@ namespace BizTalkCheckmkPulse
var total = result.SuspendedInstances.Count; var total = result.SuspendedInstances.Count;
var resumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.Resumable); var resumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.Resumable);
var nonresumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.NonResumable); var nonresumable = result.SuspendedInstances.Count(x => x.Kind == SuspendedKind.NonResumable);
var state = DetermineSuspensionState(resumable, nonresumable); var routingFailureReports = result.SuspendedInstances.Count(x => x.IsRoutingFailureReport);
var state = DetermineSuspensionState(resumable, nonresumable, routingFailureReports);
var metrics = string.Format( var metrics = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0", "biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0|biztalk_routing_failure_reports={5};;{6};0",
total, total,
resumable, resumable,
_options.WarnResumableThreshold, _options.WarnResumableThreshold,
nonresumable, nonresumable,
_options.CritNonResumableThreshold); _options.CritNonResumableThreshold,
routingFailureReports,
_options.CritRoutingFailureThreshold);
var detail = total == 0 var detail = total == 0
? "No suspended BizTalk service instances found." ? "Suspended total=0, resumable=0, nonresumable=0, routing_failure_reports=0."
: BuildSuspensionDetail(result.SuspendedInstances, total, resumable, nonresumable); : BuildSuspensionDetail(result.SuspendedInstances, total, resumable, nonresumable, routingFailureReports);
return BuildLine(state, _options.ServiceName("Suspended Instances"), metrics, detail); return BuildLine(state, _options.ServiceName("Suspended Instances"), metrics, detail);
} }
@@ -245,65 +259,147 @@ namespace BizTalkCheckmkPulse
return BuildLine(state, _options.ServiceName("Host Instances"), metrics, detail.ToString()); return BuildLine(state, _options.ServiceName("Host Instances"), metrics, detail.ToString());
} }
/// <summary> private string FormatReceiveLocations(ProbeResult result)
/// Formatiert aggregierte Receive-Location-, Send-Port- und Orchestration-Zustaende.
/// </summary>
/// <param name="result">Probe-Ergebnis.</param>
/// <returns>Checkmk-Zeile fuer Runtime Artifacts.</returns>
private string FormatRuntimeArtifacts(ProbeResult result)
{ {
if (!result.Platform.RuntimeArtifactsDataAvailable) if (!result.Platform.ReceiveLocationsDataAvailable)
{ {
var errorDetail = "BizTalk Runtime Artifacts konnten nicht vollstaendig gelesen werden. Teilwerte werden nicht als OK gewertet.";
return BuildLine( return BuildLine(
CheckState.Unknown, CheckState.Unknown,
_options.ServiceName("Runtime Artifacts"), _options.ServiceName("Receive Locations"),
"-", "-",
AppendDiagnostics(errorDetail, SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_ReceiveLocation", "MSBTS_SendPort", "MSBTS_Orchestration"))); AppendDiagnostics(
"Receive Locations konnten nicht verlaesslich gelesen werden.",
SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_ReceiveLocation")));
} }
var receiveLocations = result.Applications.Sum(x => x.ReceiveLocationTotal); var disabled = result.ReceiveLocations.Where(x => x.IsDisabled == true).ToArray();
var receiveLocationsDisabled = result.Applications.Sum(x => x.ReceiveLocationDisabled); var unknown = result.ReceiveLocations.Where(x => !x.IsDisabled.HasValue).ToArray();
var sendPorts = result.Applications.Sum(x => x.SendPortTotal); var expectedDisabled = disabled
var sendPortsStarted = result.Applications.Sum(x => x.SendPortStarted); .Where(x => _options.IsExpectedDisabledReceiveLocation(x.ApplicationName, x.Name))
var sendPortsInactive = result.Applications.Sum(x => x.SendPortStopped + x.SendPortBound); .ToArray();
var sendPortsUnknown = result.Applications.Sum(x => x.SendPortUnknown); var unexpectedDisabled = disabled.Except(expectedDisabled).ToArray();
var orchestrations = result.Applications.Sum(x => x.OrchestrationTotal); var enabled = result.ReceiveLocations.Count - disabled.Length - unknown.Length;
var orchestrationsStarted = result.Applications.Sum(x => x.OrchestrationStarted); var state = unknown.Length > 0
var orchestrationsInactive = result.Applications.Sum(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound); ? CheckState.Unknown
var orchestrationsUnknown = result.Applications.Sum(x => x.OrchestrationUnknown); : _options.AlertOnArtifactRuntimeIssues && unexpectedDisabled.Length > 0
var state = sendPortsUnknown > 0 || orchestrationsUnknown > 0
? CheckState.Critical ? CheckState.Critical
: _options.AlertOnArtifactRuntimeIssues && (receiveLocationsDisabled > 0 || sendPortsInactive > 0 || orchestrationsInactive > 0) : CheckState.Ok;
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_receive_locations_total={0};;;0|biztalk_receive_locations_enabled={1};;;0|biztalk_receive_locations_disabled={2};;;0|biztalk_receive_locations_unexpected_disabled={3};;1;0|biztalk_receive_locations_expected_disabled={4};;;0|biztalk_receive_locations_unknown={5};;1;0",
result.ReceiveLocations.Count,
enabled,
disabled.Length,
unexpectedDisabled.Length,
expectedDisabled.Length,
unknown.Length);
var detail = new StringBuilder()
.Append("Receive locations total=").Append(result.ReceiveLocations.Count)
.Append(", enabled=").Append(enabled)
.Append(", unexpected_disabled=").Append(unexpectedDisabled.Length)
.Append(", expected_disabled=").Append(expectedDisabled.Length)
.Append(", unknown=").Append(unknown.Length);
AppendLimitedList(detail, "affected", unexpectedDisabled.Concat(unknown).Select(ReceiveLocationDisplay));
return BuildLine(state, _options.ServiceName("Receive Locations"), metrics, detail.ToString());
}
private string FormatSendPorts(ProbeResult result)
{
if (!result.Platform.SendPortsDataAvailable)
{
return BuildLine(
CheckState.Unknown,
_options.ServiceName("Send Ports"),
"-",
AppendDiagnostics(
"Send Ports konnten nicht verlaesslich gelesen werden.",
SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_SendPort")));
}
var started = result.SendPorts.Count(x => x.Status == SendPortStarted);
var stopped = result.SendPorts.Count(x => x.Status == SendPortStopped);
var bound = result.SendPorts.Count(x => x.Status == SendPortBound);
var unknown = result.SendPorts.Where(x => x.Status != SendPortStarted && x.Status != SendPortStopped && x.Status != SendPortBound).ToArray();
var inactive = result.SendPorts.Where(x => x.Status == SendPortStopped || x.Status == SendPortBound).ToArray();
var expectedInactive = inactive
.Where(x => _options.IsExpectedInactiveSendPort(x.ApplicationName, x.Name))
.ToArray();
var unexpectedInactive = inactive.Except(expectedInactive).ToArray();
var state = unknown.Length > 0 || (_options.AlertOnArtifactRuntimeIssues && unexpectedInactive.Length > 0)
? CheckState.Critical
: CheckState.Ok;
var metrics = string.Format(
CultureInfo.InvariantCulture,
"biztalk_send_ports_total={0};;;0|biztalk_send_ports_started={1};;;0|biztalk_send_ports_stopped={2};;;0|biztalk_send_ports_bound={3};;;0|biztalk_send_ports_unexpected_inactive={4};;1;0|biztalk_send_ports_expected_inactive={5};;;0|biztalk_send_ports_unknown={6};;1;0",
result.SendPorts.Count,
started,
stopped,
bound,
unexpectedInactive.Length,
expectedInactive.Length,
unknown.Length);
var detail = new StringBuilder()
.Append("Send ports total=").Append(result.SendPorts.Count)
.Append(", started=").Append(started)
.Append(", stopped=").Append(stopped)
.Append(", bound=").Append(bound)
.Append(", unexpected_inactive=").Append(unexpectedInactive.Length)
.Append(", expected_inactive=").Append(expectedInactive.Length)
.Append(", unknown=").Append(unknown.Length);
AppendLimitedList(detail, "affected", unexpectedInactive.Concat(unknown).Select(SendPortDisplay));
return BuildLine(state, _options.ServiceName("Send Ports"), metrics, detail.ToString());
}
private string FormatOrchestrations(ProbeResult result)
{
if (!result.Platform.OrchestrationsDataAvailable)
{
return BuildLine(
CheckState.Unknown,
_options.ServiceName("Orchestrations"),
"-",
AppendDiagnostics(
"Orchestrations konnten nicht verlaesslich gelesen werden.",
SelectDiagnostics(result, DiagnosticArea.Wmi, "WMI namespace connection", "MSBTS_Orchestration")));
}
var total = result.Applications.Sum(x => x.OrchestrationTotal);
var started = result.Applications.Sum(x => x.OrchestrationStarted);
var stopped = result.Applications.Sum(x => x.OrchestrationStopped);
var bound = result.Applications.Sum(x => x.OrchestrationBound);
var unbound = result.Applications.Sum(x => x.OrchestrationUnbound);
var unknown = result.Applications.Sum(x => x.OrchestrationUnknown);
var inactive = stopped + bound + unbound;
var state = unknown > 0
? CheckState.Critical
: _options.AlertOnInactiveOrchestrations && inactive > 0
? CheckState.Warning ? CheckState.Warning
: CheckState.Ok; : CheckState.Ok;
var metrics = string.Format( var metrics = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"biztalk_applications={0};;;0|biztalk_receive_locations={1};;;0|biztalk_receive_locations_disabled={2};1;;0|biztalk_send_ports={3};;;0|biztalk_send_ports_started={4};;;0|biztalk_send_ports_inactive={5};1;;0|biztalk_send_ports_unknown={6};;1;0|biztalk_orchestrations={7};;;0|biztalk_orchestrations_started={8};;;0|biztalk_orchestrations_inactive={9};1;;0|biztalk_orchestrations_unknown={10};;1;0", "biztalk_orchestrations_total={0};;;0|biztalk_orchestrations_started={1};;;0|biztalk_orchestrations_stopped={2};;;0|biztalk_orchestrations_bound={3};;;0|biztalk_orchestrations_unbound={4};;;0|biztalk_orchestrations_unknown={5};;1;0",
result.Applications.Count, total,
receiveLocations, started,
receiveLocationsDisabled, stopped,
sendPorts, bound,
sendPortsStarted, unbound,
sendPortsInactive, unknown);
sendPortsUnknown, var detail = new StringBuilder()
orchestrations, .Append("Orchestrations total=").Append(total)
orchestrationsStarted, .Append(", started=").Append(started)
orchestrationsInactive, .Append(", stopped=").Append(stopped)
orchestrationsUnknown); .Append(", bound=").Append(bound)
.Append(", unbound=").Append(unbound)
var detail = new StringBuilder(); .Append(", unknown=").Append(unknown);
detail.Append("Applications=").Append(result.Applications.Count)
.Append("; receive_locations total=").Append(receiveLocations).Append(", disabled=").Append(receiveLocationsDisabled)
.Append("; send_ports total=").Append(sendPorts).Append(", started=").Append(sendPortsStarted).Append(", inactive=").Append(sendPortsInactive).Append(", unknown=").Append(sendPortsUnknown)
.Append("; orchestrations total=").Append(orchestrations).Append(", started=").Append(orchestrationsStarted).Append(", inactive=").Append(orchestrationsInactive).Append(", unknown=").Append(orchestrationsUnknown);
var affected = result.Applications var affected = result.Applications
.Where(x => x.ReceiveLocationDisabled > 0 || x.SendPortStopped + x.SendPortBound + x.SendPortUnknown > 0 || x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound + x.OrchestrationUnknown > 0) .Where(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound + x.OrchestrationUnknown > 0)
.Take(_options.MaxSummaryItems) .OrderByDescending(x => x.OrchestrationUnknown)
.Select(x => x.ApplicationName + "(rl_disabled=" + x.ReceiveLocationDisabled + ", sp_inactive=" + (x.SendPortStopped + x.SendPortBound) + ", orch_inactive=" + (x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound) + ")"); .ThenByDescending(x => x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound)
AppendList(detail, "notable_apps", affected); .Select(x => x.ApplicationName + "(inactive="
return BuildLine(state, _options.ServiceName("Runtime Artifacts"), metrics, detail.ToString()); + (x.OrchestrationStopped + x.OrchestrationBound + x.OrchestrationUnbound)
+ ",unknown=" + x.OrchestrationUnknown + ")");
AppendLimitedList(detail, "affected_apps", affected);
return BuildLine(state, _options.ServiceName("Orchestrations"), metrics, detail.ToString());
} }
/// <summary> /// <summary>
@@ -362,16 +458,24 @@ namespace BizTalkCheckmkPulse
{ {
var resumable = group.Count(x => x.Kind == SuspendedKind.Resumable); var resumable = group.Count(x => x.Kind == SuspendedKind.Resumable);
var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable); var nonresumable = group.Count(x => x.Kind == SuspendedKind.NonResumable);
var state = DetermineSuspensionState(resumable, nonresumable); var routingFailureReports = group.Count(x => x.IsRoutingFailureReport);
var state = DetermineSuspensionState(resumable, nonresumable, routingFailureReports);
var metrics = string.Format( var metrics = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0", "biztalk_suspended_total={0};;;0|biztalk_suspended_resumable={1};{2};;0|biztalk_suspended_nonresumable={3};;{4};0|biztalk_routing_failure_reports={5};;{6};0",
group.Count(), group.Count(),
resumable, resumable,
_options.WarnResumableThreshold, _options.WarnResumableThreshold,
nonresumable, nonresumable,
_options.CritNonResumableThreshold); _options.CritNonResumableThreshold,
yield return BuildLine(state, _options.ServiceName("Suspended " + group.Key), metrics, "Suspended instances: resumable=" + resumable + ", nonresumable=" + nonresumable); routingFailureReports,
_options.CritRoutingFailureThreshold);
yield return BuildLine(
state,
_options.ServiceName("Suspended " + group.Key),
metrics,
"Suspended total=" + group.Count() + ", resumable=" + resumable
+ ", nonresumable=" + nonresumable + ", routing_failure_reports=" + routingFailureReports);
} }
} }
@@ -381,8 +485,14 @@ namespace BizTalkCheckmkPulse
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param> /// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param> /// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
/// <returns>Zustand gemaess konfigurierten Schwellwerten.</returns> /// <returns>Zustand gemaess konfigurierten Schwellwerten.</returns>
private CheckState DetermineSuspensionState(int resumable, int nonresumable) private CheckState DetermineSuspensionState(int resumable, int nonresumable, int routingFailureReports)
{ {
if (_options.CritRoutingFailureThreshold > 0
&& routingFailureReports >= _options.CritRoutingFailureThreshold)
{
return CheckState.Critical;
}
if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold) if (_options.CritNonResumableThreshold > 0 && nonresumable >= _options.CritNonResumableThreshold)
{ {
return CheckState.Critical; return CheckState.Critical;
@@ -404,24 +514,32 @@ namespace BizTalkCheckmkPulse
/// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param> /// <param name="resumable">Anzahl fortsetzbarer Instanzen.</param>
/// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param> /// <param name="nonresumable">Anzahl nicht fortsetzbarer Instanzen.</param>
/// <returns>Kompakter Detailtext.</returns> /// <returns>Kompakter Detailtext.</returns>
private string BuildSuspensionDetail(IEnumerable<SuspendedInstance> instances, int total, int resumable, int nonresumable) private string BuildSuspensionDetail(
IEnumerable<SuspendedInstance> instances,
int total,
int resumable,
int nonresumable,
int routingFailureReports)
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
builder.Append(total).Append(" suspended BizTalk service instance(s): resumable=").Append(resumable) builder.Append("Suspended total=").Append(total)
.Append(", nonresumable=").Append(nonresumable); .Append(", resumable=").Append(resumable)
.Append(", nonresumable=").Append(nonresumable)
.Append(", routing_failure_reports=").Append(routingFailureReports);
var apps = instances var apps = instances
.Where(x => !x.IsRoutingFailureReport
&& !string.Equals(x.ApplicationName, "(unknown)", StringComparison.OrdinalIgnoreCase))
.GroupBy(x => x.ApplicationName) .GroupBy(x => x.ApplicationName)
.OrderByDescending(x => x.Count()) .OrderByDescending(x => x.Count())
.Take(_options.MaxSummaryItems) .Select(x => x.Key + "(R=" + x.Count(y => y.Kind == SuspendedKind.Resumable) + ",NR=" + x.Count(y => y.Kind == SuspendedKind.NonResumable) + ")");
.Select(x => x.Key + "(R=" + x.Count(y => y.Kind == SuspendedKind.Resumable) + ", NR=" + x.Count(y => y.Kind == SuspendedKind.NonResumable) + ")"); AppendLimitedList(builder, "applications", apps);
AppendList(builder, "applications", apps);
var examples = instances var examples = instances
.Where(x => !x.IsRoutingFailureReport)
.OrderByDescending(x => x.SuspendTime ?? DateTime.MinValue) .OrderByDescending(x => x.SuspendTime ?? DateTime.MinValue)
.Take(Math.Min(_options.MaxSummaryItems, 5)) .Select(x => EmptyAsUnknown(x.ServiceName) + "@" + EmptyAsUnknown(x.HostName));
.Select(x => EmptyAsUnknown(x.ServiceName) + " on " + EmptyAsUnknown(x.HostName)); AppendLimitedList(builder, "recent", examples);
AppendList(builder, "examples", examples);
return builder.ToString(); return builder.ToString();
} }
@@ -433,13 +551,20 @@ namespace BizTalkCheckmkPulse
/// <param name="metrics">Performance-Daten oder Bindestrich.</param> /// <param name="metrics">Performance-Daten oder Bindestrich.</param>
/// <param name="detail">Menschenlesbare Service-Ausgabe.</param> /// <param name="detail">Menschenlesbare Service-Ausgabe.</param>
/// <returns>Vollstaendige Local-Check-Zeile.</returns> /// <returns>Vollstaendige Local-Check-Zeile.</returns>
private static string BuildLine(CheckState state, string serviceName, string metrics, string detail) private string BuildLine(CheckState state, string serviceName, string metrics, string detail)
{ {
var sanitizedDetail = SanitizeDetail(detail);
if (sanitizedDetail.Length > _options.MaxDetailCharacters)
{
sanitizedDetail = sanitizedDetail.Substring(0, _options.MaxDetailCharacters - 16).TrimEnd()
+ "... [truncated]";
}
return ((int)state).ToString(CultureInfo.InvariantCulture) return ((int)state).ToString(CultureInfo.InvariantCulture)
+ " \"" + SanitizeService(serviceName) + "\" " + " \"" + SanitizeService(serviceName) + "\" "
+ (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics) + (string.IsNullOrWhiteSpace(metrics) ? "-" : metrics)
+ " " + " "
+ SanitizeDetail(detail); + sanitizedDetail;
} }
/// <summary> /// <summary>
@@ -489,6 +614,57 @@ namespace BizTalkCheckmkPulse
} }
} }
private void AppendLimitedList(StringBuilder builder, string label, IEnumerable<string> values)
{
var all = values.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
if (all.Length == 0)
{
return;
}
var visible = all.Take(_options.MaxSummaryItems).ToArray();
builder.Append("; ").Append(label).Append("=").Append(string.Join(", ", visible));
if (all.Length > visible.Length)
{
builder.Append(" (+").Append(all.Length - visible.Length).Append(" more)");
}
}
private static string ReceiveLocationDisplay(ReceiveLocationState item)
{
return ArtifactDisplay(item.ApplicationName, item.Name);
}
private static string SendPortDisplay(SendPortState item)
{
return ArtifactDisplay(item.ApplicationName, item.Name)
+ "(" + SendPortStateName(item.Status) + ")";
}
private static string ArtifactDisplay(string applicationName, string artifactName)
{
var application = EmptyAsUnknown(applicationName);
var name = EmptyAsUnknown(artifactName);
return string.Equals(application, "(unknown)", StringComparison.OrdinalIgnoreCase)
? name
: application + "\\" + name;
}
private static string SendPortStateName(int status)
{
switch (status)
{
case SendPortStarted:
return "started";
case SendPortStopped:
return "stopped";
case SendPortBound:
return "bound";
default:
return "unknown:" + status.ToString(CultureInfo.InvariantCulture);
}
}
/// <summary> /// <summary>
/// Haengt einen optionalen benannten Wert an einen Detailtext an. /// Haengt einen optionalen benannten Wert an einen Detailtext an.
/// </summary> /// </summary>
+33 -1
View File
@@ -96,6 +96,8 @@ namespace BizTalkCheckmkPulse
Platform = new PlatformState(); Platform = new PlatformState();
HostInstances = new List<HostInstanceState>(); HostInstances = new List<HostInstanceState>();
SuspendedInstances = new List<SuspendedInstance>(); SuspendedInstances = new List<SuspendedInstance>();
ReceiveLocations = new List<ReceiveLocationState>();
SendPorts = new List<SendPortState>();
Applications = new List<ApplicationRuntimeState>(); Applications = new List<ApplicationRuntimeState>();
SqlTargets = new List<SqlAccessState>(); SqlTargets = new List<SqlAccessState>();
EventLog = new EventLogState(); EventLog = new EventLogState();
@@ -105,6 +107,8 @@ namespace BizTalkCheckmkPulse
public PlatformState Platform { get; private set; } public PlatformState Platform { get; private set; }
public List<HostInstanceState> HostInstances { get; private set; } public List<HostInstanceState> HostInstances { get; private set; }
public List<SuspendedInstance> SuspendedInstances { get; private set; } public List<SuspendedInstance> SuspendedInstances { get; private set; }
public List<ReceiveLocationState> ReceiveLocations { get; private set; }
public List<SendPortState> SendPorts { get; private set; }
public List<ApplicationRuntimeState> Applications { get; private set; } public List<ApplicationRuntimeState> Applications { get; private set; }
public List<SqlAccessState> SqlTargets { get; private set; } public List<SqlAccessState> SqlTargets { get; private set; }
public string ExecutionIdentity { get; set; } public string ExecutionIdentity { get; set; }
@@ -121,7 +125,9 @@ namespace BizTalkCheckmkPulse
public bool PlatformDataAvailable { get; set; } public bool PlatformDataAvailable { get; set; }
public bool HostInstancesDataAvailable { get; set; } public bool HostInstancesDataAvailable { get; set; }
public bool SuspendedInstancesDataAvailable { get; set; } public bool SuspendedInstancesDataAvailable { get; set; }
public bool RuntimeArtifactsDataAvailable { get; set; } public bool ReceiveLocationsDataAvailable { get; set; }
public bool SendPortsDataAvailable { get; set; }
public bool OrchestrationsDataAvailable { get; set; }
public string ServerName { get; set; } public string ServerName { get; set; }
public string GroupName { get; set; } public string GroupName { get; set; }
public string OperatorGroup { get; set; } public string OperatorGroup { get; set; }
@@ -169,7 +175,33 @@ namespace BizTalkCheckmkPulse
public string InstanceId { get; set; } public string InstanceId { get; set; }
public string ErrorDescription { get; set; } public string ErrorDescription { get; set; }
public DateTime? SuspendTime { get; set; } public DateTime? SuspendTime { get; set; }
public int ServiceClassId { get; set; }
public SuspendedKind Kind { get; set; } public SuspendedKind Kind { get; set; }
public bool IsRoutingFailureReport
{
get { return ServiceClassId == 64; }
}
}
/// <summary>
/// Konfigurations- und Laufzeitzustand einer Receive Location.
/// </summary>
internal sealed class ReceiveLocationState
{
public string Name { get; set; }
public string ApplicationName { get; set; }
public bool? IsDisabled { get; set; }
}
/// <summary>
/// Laufzeitzustand eines Send Ports.
/// </summary>
internal sealed class SendPortState
{
public string Name { get; set; }
public string ApplicationName { get; set; }
public int Status { get; set; }
} }
/// <summary> /// <summary>
+60 -2
View File
@@ -20,8 +20,13 @@ namespace BizTalkCheckmkPulse
public int SqlConnectionTimeoutSeconds { get; set; } public int SqlConnectionTimeoutSeconds { get; set; }
public int WarnResumableThreshold { get; set; } public int WarnResumableThreshold { get; set; }
public int CritNonResumableThreshold { get; set; } public int CritNonResumableThreshold { get; set; }
public int CritRoutingFailureThreshold { get; set; }
public int MaxSummaryItems { get; set; } public int MaxSummaryItems { get; set; }
public int MaxDetailCharacters { get; set; }
public bool AlertOnArtifactRuntimeIssues { get; set; } public bool AlertOnArtifactRuntimeIssues { get; set; }
public bool AlertOnInactiveOrchestrations { get; set; }
public IReadOnlyList<string> ExpectedDisabledReceiveLocations { get; set; }
public IReadOnlyList<string> ExpectedInactiveSendPorts { get; set; }
public bool EmitPerApplicationSuspensionServices { get; set; } public bool EmitPerApplicationSuspensionServices { get; set; }
public bool ProbeEventLog { get; set; } public bool ProbeEventLog { get; set; }
public int EventLogLookbackMinutes { get; set; } public int EventLogLookbackMinutes { get; set; }
@@ -49,8 +54,13 @@ namespace BizTalkCheckmkPulse
SqlConnectionTimeoutSeconds = 5; SqlConnectionTimeoutSeconds = 5;
WarnResumableThreshold = 1; WarnResumableThreshold = 1;
CritNonResumableThreshold = 1; CritNonResumableThreshold = 1;
MaxSummaryItems = 12; CritRoutingFailureThreshold = 1;
AlertOnArtifactRuntimeIssues = false; MaxSummaryItems = 5;
MaxDetailCharacters = 1600;
AlertOnArtifactRuntimeIssues = true;
AlertOnInactiveOrchestrations = false;
ExpectedDisabledReceiveLocations = new string[0];
ExpectedInactiveSendPorts = new string[0];
EmitPerApplicationSuspensionServices = false; EmitPerApplicationSuspensionServices = false;
ProbeEventLog = true; ProbeEventLog = true;
EventLogLookbackMinutes = 60; EventLogLookbackMinutes = 60;
@@ -96,8 +106,13 @@ namespace BizTalkCheckmkPulse
options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60); options.SqlConnectionTimeoutSeconds = ReadInt(settings, "SqlConnectionTimeoutSeconds", options.SqlConnectionTimeoutSeconds, 3, 60);
options.WarnResumableThreshold = ReadInt(settings, "WarnResumableThreshold", options.WarnResumableThreshold, 0, 1000000); options.WarnResumableThreshold = ReadInt(settings, "WarnResumableThreshold", options.WarnResumableThreshold, 0, 1000000);
options.CritNonResumableThreshold = ReadInt(settings, "CritNonResumableThreshold", options.CritNonResumableThreshold, 0, 1000000); options.CritNonResumableThreshold = ReadInt(settings, "CritNonResumableThreshold", options.CritNonResumableThreshold, 0, 1000000);
options.CritRoutingFailureThreshold = ReadInt(settings, "CritRoutingFailureThreshold", options.CritRoutingFailureThreshold, 0, 1000000);
options.MaxSummaryItems = ReadInt(settings, "MaxSummaryItems", options.MaxSummaryItems, 1, 100); options.MaxSummaryItems = ReadInt(settings, "MaxSummaryItems", options.MaxSummaryItems, 1, 100);
options.MaxDetailCharacters = ReadInt(settings, "MaxDetailCharacters", options.MaxDetailCharacters, 256, 8192);
options.AlertOnArtifactRuntimeIssues = ReadBool(settings, "AlertOnArtifactRuntimeIssues", options.AlertOnArtifactRuntimeIssues); options.AlertOnArtifactRuntimeIssues = ReadBool(settings, "AlertOnArtifactRuntimeIssues", options.AlertOnArtifactRuntimeIssues);
options.AlertOnInactiveOrchestrations = ReadBool(settings, "AlertOnInactiveOrchestrations", options.AlertOnInactiveOrchestrations);
options.ExpectedDisabledReceiveLocations = ReadExactList(ReadString(settings, "ExpectedDisabledReceiveLocations", string.Empty));
options.ExpectedInactiveSendPorts = ReadExactList(ReadString(settings, "ExpectedInactiveSendPorts", string.Empty));
options.EmitPerApplicationSuspensionServices = ReadBool(settings, "EmitPerApplicationSuspensionServices", options.EmitPerApplicationSuspensionServices); options.EmitPerApplicationSuspensionServices = ReadBool(settings, "EmitPerApplicationSuspensionServices", options.EmitPerApplicationSuspensionServices);
options.ProbeEventLog = ReadBool(settings, "ProbeEventLog", options.ProbeEventLog); options.ProbeEventLog = ReadBool(settings, "ProbeEventLog", options.ProbeEventLog);
options.EventLogLookbackMinutes = ReadInt(settings, "EventLogLookbackMinutes", options.EventLogLookbackMinutes, 1, 10080); options.EventLogLookbackMinutes = ReadInt(settings, "EventLogLookbackMinutes", options.EventLogLookbackMinutes, 1, 10080);
@@ -245,6 +260,49 @@ namespace BizTalkCheckmkPulse
.ToArray(); .ToArray();
} }
/// <summary>
/// Liest eine Pipe-getrennte, exakte Allowlist. Wildcards werden bewusst nicht interpretiert.
/// </summary>
private static IReadOnlyList<string> ReadExactList(string value)
{
return (value ?? string.Empty)
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.Where(x => x.Length > 0)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
internal bool IsExpectedDisabledReceiveLocation(string applicationName, string artifactName)
{
return MatchesArtifact(ExpectedDisabledReceiveLocations, applicationName, artifactName);
}
internal bool IsExpectedInactiveSendPort(string applicationName, string artifactName)
{
return MatchesArtifact(ExpectedInactiveSendPorts, applicationName, artifactName);
}
private static bool MatchesArtifact(
IEnumerable<string> configuredEntries,
string applicationName,
string artifactName)
{
var name = (artifactName ?? string.Empty).Trim();
var application = (applicationName ?? string.Empty).Trim();
if (name.Length == 0)
{
return false;
}
var qualified = application.Length == 0 || string.Equals(application, "(unknown)", StringComparison.OrdinalIgnoreCase)
? string.Empty
: application + "\\" + name;
return configuredEntries.Any(x =>
string.Equals(x, name, StringComparison.OrdinalIgnoreCase)
|| (qualified.Length > 0 && string.Equals(x, qualified, StringComparison.OrdinalIgnoreCase)));
}
/// <summary> /// <summary>
/// Prueft einen Wert ohne Beachtung der Gross-/Kleinschreibung gegen Kandidaten. /// Prueft einen Wert ohne Beachtung der Gross-/Kleinschreibung gegen Kandidaten.
/// </summary> /// </summary>
+19
View File
@@ -70,6 +70,11 @@ namespace BizTalkCheckmkPulse
{ {
logger.Info("Collection started. snapshot=" + options.SnapshotPath); logger.Info("Collection started. snapshot=" + options.SnapshotPath);
var result = Collect(options); var result = Collect(options);
foreach (var diagnostic in result.Diagnostics)
{
logger.Warning("Probe diagnostic: " + diagnostic.ToDisplayText());
}
var lines = formatter.Format(result).ToArray(); var lines = formatter.Format(result).ToArray();
new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes) new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
.Write(lines, DateTime.UtcNow, CurrentIdentity()); .Write(lines, DateTime.UtcNow, CurrentIdentity());
@@ -79,6 +84,20 @@ namespace BizTalkCheckmkPulse
+ lines.Length + lines.Length
+ " diagnostics=" + " diagnostics="
+ result.Diagnostics.Count + result.Diagnostics.Count
+ " suspended_total="
+ result.SuspendedInstances.Count
+ " routing_failure_reports="
+ result.SuspendedInstances.Count(x => x.IsRoutingFailureReport)
+ " receive_locations_total="
+ result.ReceiveLocations.Count
+ " receive_locations_disabled="
+ result.ReceiveLocations.Count(x => x.IsDisabled == true)
+ " receive_locations_unknown="
+ result.ReceiveLocations.Count(x => !x.IsDisabled.HasValue)
+ " send_ports_total="
+ result.SendPorts.Count
+ " send_ports_not_started="
+ result.SendPorts.Count(x => x.Status != 3)
+ " elapsed_ms=" + " elapsed_ms="
+ stopwatch.ElapsedMilliseconds); + stopwatch.ElapsedMilliseconds);
return 0; return 0;
@@ -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.0.0.0")] [assembly: AssemblyVersion("2.1.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyFileVersion("2.1.0.0")]
+3 -3
View File
@@ -15,7 +15,7 @@ namespace BizTalkCheckmkPulse
/// </summary> /// </summary>
internal sealed class SnapshotStore internal sealed class SnapshotStore
{ {
internal const string Magic = "BIZTALK_CHECKMK_PULSE_SNAPSHOT_V1"; internal const string Magic = "BIZTALK_CHECKMK_PULSE_SNAPSHOT_V2";
private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(false, true); private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(false, true);
private readonly string _path; private readonly string _path;
private readonly int _maxBytes; private readonly int _maxBytes;
@@ -258,9 +258,9 @@ namespace BizTalkCheckmkPulse
} }
} }
if (count < 6) if (count < 8)
{ {
throw new InvalidDataException("Snapshot must contain all six stable services."); throw new InvalidDataException("Snapshot must contain all eight stable services.");
} }
} }
+39 -13
View File
@@ -26,6 +26,8 @@ namespace BizTalkCheckmkPulse
private const int EAccessDenied = unchecked((int)0x80070005); private const int EAccessDenied = unchecked((int)0x80070005);
internal const string GroupSettingQuery = internal const string GroupSettingQuery =
"SELECT Name, BizTalkOperatorGroup, BizTalkReadOnlyUserGroup, MgmtDbServerName, MgmtDbName, SubscriptionDBServerName, SubscriptionDBName FROM MSBTS_GroupSetting"; "SELECT Name, BizTalkOperatorGroup, BizTalkReadOnlyUserGroup, MgmtDbServerName, MgmtDbName, SubscriptionDBServerName, SubscriptionDBName FROM MSBTS_GroupSetting";
internal const string SuspendedInstancesQuery =
"SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32 OR ServiceClass = 64";
private readonly MonitoringOptions _options; private readonly MonitoringOptions _options;
/// <summary> /// <summary>
@@ -58,7 +60,7 @@ namespace BizTalkCheckmkPulse
result.Platform.PlatformDataAvailable = QueryPlatform(scope, result); result.Platform.PlatformDataAvailable = QueryPlatform(scope, result);
result.Platform.HostInstancesDataAvailable = QueryHostInstances(scope, result, server); result.Platform.HostInstancesDataAvailable = QueryHostInstances(scope, result, server);
var applicationIndex = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var applicationIndex = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
result.Platform.RuntimeArtifactsDataAvailable = QueryApplicationRuntime(scope, result, applicationIndex); QueryApplicationRuntime(scope, result, applicationIndex);
if (_options.EmitPerApplicationSuspensionServices) if (_options.EmitPerApplicationSuspensionServices)
{ {
AddArtifactIndex(scope, result, applicationIndex, "MSBTS_ReceivePort"); AddArtifactIndex(scope, result, applicationIndex, "MSBTS_ReceivePort");
@@ -196,31 +198,52 @@ namespace BizTalkCheckmkPulse
/// <param name="scope">Verbundener BizTalk-WMI-Scope.</param> /// <param name="scope">Verbundener BizTalk-WMI-Scope.</param>
/// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param> /// <param name="result">Zu aktualisierendes Ergebnisobjekt.</param>
/// <param name="applicationIndex">Best-Effort-Index fuer Suspensionsdetails.</param> /// <param name="applicationIndex">Best-Effort-Index fuer Suspensionsdetails.</param>
/// <returns><c>true</c>, wenn alle drei Pflichtabfragen erfolgreich waren.</returns> private void QueryApplicationRuntime(
private bool QueryApplicationRuntime(
ManagementScope scope, ManagementScope scope,
ProbeResult result, ProbeResult result,
Dictionary<string, string> applicationIndex) Dictionary<string, string> applicationIndex)
{ {
var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase); var apps = new Dictionary<string, ApplicationRuntimeState>(StringComparer.OrdinalIgnoreCase);
var receiveLocationsAvailable = TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item => result.Platform.ReceiveLocationsDataAvailable = TryQuery(scope, result, "MSBTS_ReceiveLocation", "SELECT * FROM MSBTS_ReceiveLocation", item =>
{ {
AddArtifactIndexEntry(applicationIndex, item); AddArtifactIndexEntry(applicationIndex, item);
var app = GetApplication(apps, GetApplicationName(item)); var applicationName = GetApplicationName(item);
var name = FirstNonEmpty(WmiHelpers.GetString(item, "Name"), WmiHelpers.GetString(item, "ReceivePortName")) ?? "(unknown)";
bool parsedIsDisabled;
var isDisabledText = WmiHelpers.GetString(item, "IsDisabled");
var isDisabled = bool.TryParse(isDisabledText, out parsedIsDisabled)
? (bool?)parsedIsDisabled
: null;
result.ReceiveLocations.Add(new ReceiveLocationState
{
Name = name,
ApplicationName = applicationName,
IsDisabled = isDisabled
});
var app = GetApplication(apps, applicationName);
app.ReceiveLocationTotal++; app.ReceiveLocationTotal++;
if (WmiHelpers.GetBoolean(item, "IsDisabled", false)) if (isDisabled == true)
{ {
app.ReceiveLocationDisabled++; app.ReceiveLocationDisabled++;
} }
}, true); }, true);
var sendPortsAvailable = TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item => result.Platform.SendPortsDataAvailable = TryQuery(scope, result, "MSBTS_SendPort", "SELECT * FROM MSBTS_SendPort", item =>
{ {
AddArtifactIndexEntry(applicationIndex, item); AddArtifactIndexEntry(applicationIndex, item);
var app = GetApplication(apps, GetApplicationName(item)); var applicationName = GetApplicationName(item);
var name = WmiHelpers.GetString(item, "Name");
var status = WmiHelpers.GetInt32(item, "Status", 0);
result.SendPorts.Add(new SendPortState
{
Name = string.IsNullOrWhiteSpace(name) ? "(unknown)" : name,
ApplicationName = applicationName,
Status = status
});
var app = GetApplication(apps, applicationName);
app.SendPortTotal++; app.SendPortTotal++;
switch (WmiHelpers.GetInt32(item, "Status", 0)) switch (status)
{ {
case SendPortStarted: case SendPortStarted:
app.SendPortStarted++; app.SendPortStarted++;
@@ -237,7 +260,7 @@ namespace BizTalkCheckmkPulse
} }
}, true); }, true);
var orchestrationsAvailable = TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item => result.Platform.OrchestrationsDataAvailable = TryQuery(scope, result, "MSBTS_Orchestration", "SELECT * FROM MSBTS_Orchestration", item =>
{ {
AddArtifactIndexEntry(applicationIndex, item); AddArtifactIndexEntry(applicationIndex, item);
var app = GetApplication(apps, GetApplicationName(item)); var app = GetApplication(apps, GetApplicationName(item));
@@ -263,7 +286,6 @@ namespace BizTalkCheckmkPulse
}, true); }, true);
result.Applications.AddRange(apps.Values.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase)); result.Applications.AddRange(apps.Values.OrderBy(x => x.ApplicationName, StringComparer.OrdinalIgnoreCase));
return receiveLocationsAvailable && sendPortsAvailable && orchestrationsAvailable;
} }
/// <summary> /// <summary>
@@ -275,9 +297,10 @@ namespace BizTalkCheckmkPulse
/// <returns><c>true</c>, wenn die Pflichtabfrage erfolgreich war.</returns> /// <returns><c>true</c>, wenn die Pflichtabfrage erfolgreich war.</returns>
private bool QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex) private bool QuerySuspendedInstances(ManagementScope scope, ProbeResult result, Dictionary<string, string> applicationIndex)
{ {
return TryQuery(scope, result, "MSBTS_ServiceInstance", "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceStatus = 4 OR ServiceStatus = 32", item => return TryQuery(scope, result, "MSBTS_ServiceInstance", SuspendedInstancesQuery, item =>
{ {
var serviceStatus = WmiHelpers.GetInt32(item, "ServiceStatus", 0); var serviceStatus = WmiHelpers.GetInt32(item, "ServiceStatus", 0);
var serviceClass = WmiHelpers.GetInt32(item, "ServiceClass", 0);
result.SuspendedInstances.Add(new SuspendedInstance result.SuspendedInstances.Add(new SuspendedInstance
{ {
ApplicationName = ResolveApplication(item, applicationIndex), ApplicationName = ResolveApplication(item, applicationIndex),
@@ -287,7 +310,10 @@ namespace BizTalkCheckmkPulse
InstanceId = WmiHelpers.GetString(item, "InstanceID"), InstanceId = WmiHelpers.GetString(item, "InstanceID"),
ErrorDescription = WmiHelpers.GetString(item, "ErrorDescription"), ErrorDescription = WmiHelpers.GetString(item, "ErrorDescription"),
SuspendTime = WmiHelpers.GetDmtfDateTime(item, "SuspendTime"), SuspendTime = WmiHelpers.GetDmtfDateTime(item, "SuspendTime"),
Kind = serviceStatus == SuspendedNonResumable ? SuspendedKind.NonResumable : SuspendedKind.Resumable ServiceClassId = serviceClass,
Kind = serviceStatus == SuspendedNonResumable || serviceClass == 64
? SuspendedKind.NonResumable
: SuspendedKind.Resumable
}); });
}, true); }, true);
} }
+189 -7
View File
@@ -23,6 +23,11 @@ namespace BizTalkCheckmkPulse.Tests
Run("ProviderSqlLoginFailureIsPermission", ProviderSqlLoginFailureIsPermission); Run("ProviderSqlLoginFailureIsPermission", ProviderSqlLoginFailureIsPermission);
Run("RejectedSqlPrincipalIsExtracted", RejectedSqlPrincipalIsExtracted); Run("RejectedSqlPrincipalIsExtracted", RejectedSqlPrincipalIsExtracted);
Run("PlatformPermissionPropagatesToSqlDiscovery", PlatformPermissionPropagatesToSqlDiscovery); Run("PlatformPermissionPropagatesToSqlDiscovery", PlatformPermissionPropagatesToSqlDiscovery);
Run("RoutingFailureReportsAreCounted", RoutingFailureReportsAreCounted);
Run("ReceiveLocationAllowlistSeparatesExpectedState", ReceiveLocationAllowlistSeparatesExpectedState);
Run("UnknownReceiveLocationStateIsNotGreen", UnknownReceiveLocationStateIsNotGreen);
Run("SendPortAllowlistSeparatesExpectedState", SendPortAllowlistSeparatesExpectedState);
Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded);
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines); Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
Run("SnapshotRejectsTampering", SnapshotRejectsTampering); Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData); Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
@@ -47,16 +52,19 @@ namespace BizTalkCheckmkPulse.Tests
private static void SelfTestEmitsAllStableServices() private static void SelfTestEmitsAllStableServices()
{ {
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray(); var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
AssertEqual(6, lines.Length, "self-test line count"); AssertEqual(8, lines.Length, "self-test line count");
AssertEqual(6, lines.Distinct(StringComparer.Ordinal).Count(), "unique self-test lines"); AssertEqual(8, lines.Distinct(StringComparer.Ordinal).Count(), "unique self-test lines");
Assert(lines.All(x => x.StartsWith("0 \"BizTalk ", StringComparison.Ordinal)), "every self-test line must be OK"); Assert(lines.All(x => x.StartsWith("0 \"BizTalk ", StringComparison.Ordinal)), "every self-test line must be OK");
Assert(lines.Any(x => x.Contains("\"BizTalk Event Log\"")), "Event Log service missing"); Assert(lines.Any(x => x.Contains("\"BizTalk Event Log\"")), "Event Log service missing");
Assert(lines.Any(x => x.Contains("\"BizTalk Receive Locations\"")), "Receive Locations service missing");
Assert(lines.Any(x => x.Contains("\"BizTalk Send Ports\"")), "Send Ports service missing");
Assert(lines.Any(x => x.Contains("\"BizTalk Orchestrations\"")), "Orchestrations service missing");
} }
private static void UnavailableSourcesAreUnknown() private static void UnavailableSourcesAreUnknown()
{ {
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).Format(new ProbeResult()).Take(6).ToArray(); var lines = new CheckmkLocalFormatter(new MonitoringOptions()).Format(new ProbeResult()).Take(8).ToArray();
AssertEqual(6, lines.Length, "stable service count"); AssertEqual(8, lines.Length, "stable service count");
Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "unavailable sources must be UNKNOWN"); Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "unavailable sources must be UNKNOWN");
} }
@@ -65,7 +73,7 @@ namespace BizTalkCheckmkPulse.Tests
var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true }; var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true };
var result = CreateSuspensionResult("(unknown)"); var result = CreateSuspensionResult("(unknown)");
var lines = new CheckmkLocalFormatter(options).Format(result).ToArray(); var lines = new CheckmkLocalFormatter(options).Format(result).ToArray();
AssertEqual(6, lines.Length, "unknown application must not create a dynamic service"); AssertEqual(8, lines.Length, "unknown application must not create a dynamic service");
} }
private static void KnownApplicationCreatesService() private static void KnownApplicationCreatesService()
@@ -73,7 +81,7 @@ namespace BizTalkCheckmkPulse.Tests
var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true }; var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true };
var result = CreateSuspensionResult("Orders"); var result = CreateSuspensionResult("Orders");
var lines = new CheckmkLocalFormatter(options).Format(result).ToArray(); var lines = new CheckmkLocalFormatter(options).Format(result).ToArray();
AssertEqual(7, lines.Length, "known application should create one dynamic service"); AssertEqual(9, lines.Length, "known application should create one dynamic service");
Assert(lines.Any(x => x.Contains("\"BizTalk Suspended Orders\"")), "known application service missing"); Assert(lines.Any(x => x.Contains("\"BizTalk Suspended Orders\"")), "known application service missing");
} }
@@ -93,6 +101,7 @@ namespace BizTalkCheckmkPulse.Tests
Assert(query.Contains("SubscriptionDBServerName"), "master MessageBox server property missing"); Assert(query.Contains("SubscriptionDBServerName"), "master MessageBox server property missing");
Assert(query.Contains("SubscriptionDBName"), "master MessageBox database property missing"); Assert(query.Contains("SubscriptionDBName"), "master MessageBox database property missing");
Assert(query.IndexOf("MessageBoxSetting", StringComparison.OrdinalIgnoreCase) < 0, "unsupported MessageBoxSetting class present"); Assert(query.IndexOf("MessageBoxSetting", StringComparison.OrdinalIgnoreCase) < 0, "unsupported MessageBoxSetting class present");
Assert(WmiBizTalkProbe.SuspendedInstancesQuery.Contains("ServiceClass = 64"), "routing failure report filter missing");
} }
private static void ProviderSqlLoginFailureIsPermission() private static void ProviderSqlLoginFailureIsPermission()
@@ -137,6 +146,167 @@ namespace BizTalkCheckmkPulse.Tests
"SQL discovery classification"); "SQL discovery classification");
} }
private static void RoutingFailureReportsAreCounted()
{
var result = new ProbeResult();
result.Platform.SuspendedInstancesDataAvailable = true;
result.SuspendedInstances.Add(new SuspendedInstance
{
ApplicationName = "(unknown)",
ServiceName = string.Empty,
ServiceClassId = 64,
Kind = SuspendedKind.NonResumable
});
result.SuspendedInstances.Add(new SuspendedInstance
{
ApplicationName = "Orders",
ServiceName = "Receive.Order",
Kind = SuspendedKind.Resumable
});
var line = FindServiceLine(result, "Suspended Instances");
Assert(line.StartsWith("2 \"BizTalk Suspended Instances\"", StringComparison.Ordinal), "routing failure must be CRIT");
Assert(line.Contains("biztalk_suspended_total=2"), "suspended total metric missing");
Assert(line.Contains("biztalk_suspended_nonresumable=1"), "non-resumable metric missing");
Assert(line.Contains("biztalk_routing_failure_reports=1"), "routing failure metric missing");
Assert(line.Contains("routing_failure_reports=1"), "routing failure summary missing");
}
private static void ReceiveLocationAllowlistSeparatesExpectedState()
{
var options = new MonitoringOptions
{
ExpectedDisabledReceiveLocations = new[] { "Maintenance\\RL Expected" }
};
var result = new ProbeResult();
result.Platform.ReceiveLocationsDataAvailable = true;
result.ReceiveLocations.Add(new ReceiveLocationState
{
ApplicationName = "Maintenance",
Name = "RL Expected",
IsDisabled = true
});
result.ReceiveLocations.Add(new ReceiveLocationState
{
ApplicationName = "Orders",
Name = "RL Orders",
IsDisabled = true
});
result.ReceiveLocations.Add(new ReceiveLocationState
{
ApplicationName = "Orders",
Name = "RL Active",
IsDisabled = false
});
var line = FindServiceLine(result, "Receive Locations", options);
Assert(line.StartsWith("2 \"BizTalk Receive Locations\"", StringComparison.Ordinal), "unexpected disabled receive location must be CRIT");
Assert(line.Contains("biztalk_receive_locations_enabled=1"), "enabled receive metric missing");
Assert(line.Contains("biztalk_receive_locations_unexpected_disabled=1"), "unexpected disabled metric missing");
Assert(line.Contains("biztalk_receive_locations_expected_disabled=1"), "expected disabled metric missing");
Assert(line.Contains(@"Orders\RL Orders"), "affected receive location missing");
Assert(line.IndexOf("RL Expected", StringComparison.Ordinal) < 0, "expected disabled receive location should not clutter affected list");
options.ExpectedDisabledReceiveLocations = new[] { "Maintenance\\RL Expected", "RL Orders" };
var allExpected = FindServiceLine(result, "Receive Locations", options);
Assert(allExpected.StartsWith("0 \"BizTalk Receive Locations\"", StringComparison.Ordinal), "all expected disabled receive locations should be OK");
Assert(allExpected.Contains("biztalk_receive_locations_expected_disabled=2"), "all expected receive count missing");
}
private static void SendPortAllowlistSeparatesExpectedState()
{
var options = new MonitoringOptions
{
ExpectedInactiveSendPorts = new[] { "Maintenance\\SP Expected" }
};
var result = new ProbeResult();
result.Platform.SendPortsDataAvailable = true;
result.SendPorts.Add(new SendPortState
{
ApplicationName = "Maintenance",
Name = "SP Expected",
Status = 2
});
result.SendPorts.Add(new SendPortState
{
ApplicationName = "Orders",
Name = "SP Orders",
Status = 1
});
result.SendPorts.Add(new SendPortState
{
ApplicationName = "Orders",
Name = "SP Active",
Status = 3
});
var line = FindServiceLine(result, "Send Ports", options);
Assert(line.StartsWith("2 \"BizTalk Send Ports\"", StringComparison.Ordinal), "unexpected inactive send port must be CRIT");
Assert(line.Contains("biztalk_send_ports_started=1"), "started send port metric missing");
Assert(line.Contains("biztalk_send_ports_unexpected_inactive=1"), "unexpected inactive send metric missing");
Assert(line.Contains("biztalk_send_ports_expected_inactive=1"), "expected inactive send metric missing");
Assert(line.Contains(@"Orders\SP Orders(bound)"), "affected send port missing");
Assert(line.IndexOf("SP Expected", StringComparison.Ordinal) < 0, "expected inactive send port should not clutter affected list");
options.ExpectedInactiveSendPorts = new[] { "Maintenance\\SP Expected", "SP Orders" };
var allExpected = FindServiceLine(result, "Send Ports", options);
Assert(allExpected.StartsWith("0 \"BizTalk Send Ports\"", StringComparison.Ordinal), "all expected inactive send ports should be OK");
Assert(allExpected.Contains("biztalk_send_ports_expected_inactive=2"), "all expected send count missing");
}
private static void UnknownReceiveLocationStateIsNotGreen()
{
var result = new ProbeResult();
result.Platform.ReceiveLocationsDataAvailable = true;
result.ReceiveLocations.Add(new ReceiveLocationState
{
ApplicationName = "Orders",
Name = "RL Missing State",
IsDisabled = null
});
var line = FindServiceLine(result, "Receive Locations");
Assert(line.StartsWith("3 \"BizTalk Receive Locations\"", StringComparison.Ordinal), "unknown receive state must be UNKNOWN");
Assert(line.Contains("biztalk_receive_locations_unknown=1"), "unknown receive metric missing");
Assert(line.Contains(@"Orders\RL Missing State"), "unknown receive location must be visible");
}
private static void ArtifactSummaryIsBounded()
{
var options = new MonitoringOptions
{
MaxSummaryItems = 2,
MaxDetailCharacters = 256
};
var result = new ProbeResult();
result.Platform.ReceiveLocationsDataAvailable = true;
for (var i = 1; i <= 6; i++)
{
result.ReceiveLocations.Add(new ReceiveLocationState
{
ApplicationName = "Application",
Name = "Disabled-" + i,
IsDisabled = true
});
}
var line = FindServiceLine(result, "Receive Locations", options);
Assert(line.Contains("(+4 more)"), "omitted item count missing");
Assert(line.Length < 700, "local check line should remain compact");
var longResult = new ProbeResult();
longResult.Platform.ReceiveLocationsDataAvailable = true;
longResult.ReceiveLocations.Add(new ReceiveLocationState
{
ApplicationName = new string('A', 400),
Name = new string('R', 400),
IsDisabled = true
});
var truncated = FindServiceLine(longResult, "Receive Locations", options);
Assert(truncated.Contains("[truncated]"), "detail character limit should be visible");
Assert(truncated.Length < 700, "truncated local check line should remain bounded");
}
private static void SnapshotRoundTripPreservesLines() private static void SnapshotRoundTripPreservesLines()
{ {
WithTemporarySnapshot((path, store) => WithTemporarySnapshot((path, store) =>
@@ -222,7 +392,7 @@ namespace BizTalkCheckmkPulse.Tests
var lines = new CheckmkLocalFormatter(new MonitoringOptions()) var lines = new CheckmkLocalFormatter(new MonitoringOptions())
.FormatSnapshotFailure("Snapshot file does not exist.") .FormatSnapshotFailure("Snapshot file does not exist.")
.ToArray(); .ToArray();
AssertEqual(6, lines.Length, "snapshot failure stable service count"); AssertEqual(8, lines.Length, "snapshot failure stable service count");
Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "snapshot failure must be UNKNOWN"); Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "snapshot failure must be UNKNOWN");
Assert(lines.All(x => x.Contains("Scheduled Task")), "snapshot failure must contain provider action"); Assert(lines.All(x => x.Contains("Scheduled Task")), "snapshot failure must contain provider action");
} }
@@ -258,6 +428,18 @@ namespace BizTalkCheckmkPulse.Tests
return result; return result;
} }
private static string FindServiceLine(
ProbeResult result,
string serviceSuffix,
MonitoringOptions options = null)
{
var effectiveOptions = options ?? new MonitoringOptions();
var token = "\"" + effectiveOptions.ServiceName(serviceSuffix) + "\"";
return new CheckmkLocalFormatter(effectiveOptions)
.Format(result)
.Single(x => x.Contains(token));
}
private static void Run(string name, Action test) private static void Run(string name, Action test)
{ {
ExecutedTests++; ExecutedTests++;