From 951a5e72d15c563e4e26782504692d5645e48c20 Mon Sep 17 00:00:00 2001 From: Johannes Rest Date: Tue, 11 Aug 2026 10:25:25 +0200 Subject: [PATCH] Allow endpoint warnings during setup acceptance --- Dokumentation.md | 20 ++++-- README.md | 16 ++++- deployment/INSTALLATION.md | 15 +++- ...ndpoint-Reachability-Analyse-2026-08-10.md | 2 +- ...k_Checkmk_Pulse_Architekturueberblick.docx | Bin 277253 -> 277251 bytes docs/EndpointCatalog.md | 7 ++ ...ller-Runtime-Unknown-Analyse-2026-08-11.md | 67 ++++++++++++++++++ docs/Integration.md | 6 +- scripts/generate-architecture-overview.py | 10 +-- .../Properties/AssemblyInfo.cs | 4 +- src/BizTalkCheckmkPulse/Program.cs | 9 ++- .../Properties/AssemblyInfo.cs | 4 +- src/BizTalkCheckmkPulse/RuntimeValidator.cs | 58 +++++++++++---- tests/BizTalkCheckmkPulse.Tests/Program.cs | 53 +++++++++++++- 14 files changed, 234 insertions(+), 37 deletions(-) create mode 100644 docs/Installer-Runtime-Unknown-Analyse-2026-08-11.md diff --git a/Dokumentation.md b/Dokumentation.md index f6ff9c1..5f0cb37 100644 --- a/Dokumentation.md +++ b/Dokumentation.md @@ -414,8 +414,8 @@ dem echten Collector-Konto und erzwingt einen vollständigen Endpoint- Katalogabgleich. Der Installer wartet begrenzt auf `LastTaskResult=0` und lässt anschließend die installierte Produkt-EXE Snapshot und Katalog mit den normalen Produktparsern validieren. Der Snapshot muss aus diesem Lauf stammen, die -erwartete Identität und alle neun eindeutigen Services ohne `UNKNOWN` -enthalten. Erst dann wird der normale Minutentask registriert und das Backup +erwartete Identität und alle neun eindeutigen Services enthalten. Erst dann +wird der normale Minutentask registriert und das Backup gelöscht. Bei Fehler oder Timeout wird die alte Version wiederhergestellt und ein frischer Lauf des alten Tasks abgewartet. @@ -435,6 +435,17 @@ Abnahmefehler und das Rollback-Ergebnis. Das Collector-Kennwort wird der Logging-Komponente nie übergeben. Ein frisch abgeschlossener Task mit Fehlercode wird sofort erkannt; der Fehler erscheint dezimal und hexadezimal. +Version 2.2.6 behandelt einen fachlichen UNKNOWN-Zustand der Endpoint-Prüfung +nicht mehr als Installationsdefekt. Sind Providerlauf, Snapshot, Identität, +neun Services und der erzwungen aktualisierte Katalog technisch valide, darf +`BizTalk Endpoint Reachability` als einziger Service `UNKNOWN` bleiben. Die +vollständige Local-Check-Zeile wird als Abnahmewarnung ausgegeben und bleibt +unverändert für Checkmk erhalten. Damit wird eine unvollständige +Endpoint-Auflösung nicht grüngefärbt und zugleich kein funktionsfähiges Update +zurückgerollt. UNKNOWN in Platform, SQL Access, Suspensions, Host Instances, +Receive Locations, Send Ports, Orchestrations oder Event Log bleibt +blockierend, weil dort die Kern-Datenerhebung nicht zuverlässig belegt ist. + Der installierte Task selbst läuft mit `RunLevel Limited`. Der Installer übergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle; es wird weder in einer Prozesskommandozeile noch in Config oder Log @@ -634,8 +645,9 @@ Automatisiert: - erzwungener Installations-Katalogabgleich wird unabhängig vom Katalogalter ausgeführt, - Runtime-Abnahme weist alten/manipulierten Snapshot, falsche Identität, - fehlenden Service, `UNKNOWN`, alten/ungültigen Katalog und fehlerhaften oder - nicht rechtzeitig beendeten Tasklauf zurück, + fehlenden Service, Kernservice-`UNKNOWN`, alten/ungültigen Katalog und + fehlerhaften oder nicht rechtzeitig beendeten Tasklauf zurück; ein isoliertes + Endpoint-Reachability-`UNKNOWN` wird mit vollständigem Detail akzeptiert, - Snapshot-Roundtrip und Ersatz, - SHA-256-Manipulation wird verworfen, - Stale-Snapshot wird verworfen, diff --git a/README.md b/README.md index ce69357..18e6206 100644 --- a/README.md +++ b/README.md @@ -267,7 +267,7 @@ als `UNKNOWN`. Ein gMSA kann über die Checkbox ohne Kennworteingabe installiert werden; die produktive Standardbeschreibung geht vom normalen Servicekonto aus. -Der Installer (ab Version 2.2.5): +Der Installer (ab Version 2.2.6): 1. prüft Paket und Staging zwingend auf neun Self-Test-Zeilen, bevor der bestehende Task angehalten wird. Der Servicevertrag der installierten @@ -285,8 +285,12 @@ Der Installer (ab Version 2.2.5): 6. wartet höchstens vier Minuten auf `LastTaskResult=0`, bricht bei einem bereits abgeschlossenen Fehlerlauf sofort mit Dezimal-/Hexcode ab und validiert danach mit der Produkt-EXE einen erst nach Installationsbeginn erzeugten Snapshot, - die Collector-Identität, alle neun eindeutigen Services ohne `UNKNOWN` - sowie den frischen, maschinen- und umgebungsgebundenen Endpoint-Katalog, + die Collector-Identität, alle neun eindeutigen Services sowie den frischen, + maschinen- und umgebungsgebundenen Endpoint-Katalog. `UNKNOWN` in einem + Kernservice bleibt ein Abnahmefehler. Ein ausschließlich bei + `BizTalk Endpoint Reachability` auftretendes `UNKNOWN` wird als sichtbare + Betriebswarnung übernommen, weil es eine unvollständige Prüfabdeckung und + keinen defekten Installationslauf beschreibt, 7. registriert erst nach dieser Abnahme den normalen Minutentask; andernfalls werden vorherige Version, Wrapper und Task samt frischem Altsnapshot wiederhergestellt, @@ -323,6 +327,12 @@ Die Analyse des in Version 2.2.4 beobachteten Abbruchs mit [docs/Installer-Self-Test-Analyse-2026-08-11.md](docs/Installer-Self-Test-Analyse-2026-08-11.md) dokumentiert. +Der nachfolgende ACC-Abbruch mit einem technisch validen Snapshot und +`UNKNOWN=[BizTalk Endpoint Reachability]` ist in +[docs/Installer-Runtime-Unknown-Analyse-2026-08-11.md](docs/Installer-Runtime-Unknown-Analyse-2026-08-11.md) +analysiert. Der Zustand wird nicht grüngefärbt: Setup 2.2.6 schließt ab und +gibt die vollständige UNKNOWN-Zeile als Warnung aus; Checkmk behält Status 3. + Snapshot und Consumer: ```cmd diff --git a/deployment/INSTALLATION.md b/deployment/INSTALLATION.md index bf13d42..70ce1e9 100644 --- a/deployment/INSTALLATION.md +++ b/deployment/INSTALLATION.md @@ -45,6 +45,17 @@ Version mit acht Services auf die Version mit dem zusätzlichen Service `BizTalk Endpoint Reachability` aktualisiert werden. Der frühere Abbruch `Exitcode=0, Zeilen=8` betraf die Altversion und war kein Fehler im neuen Paket. +Version 2.2.6 trennt technische Installationsabnahme und fachlichen +Endpoint-Zustand. Ein frischer, integritäts- und identitätsgeprüfter Snapshot +mit allen neun Services und ein frisch synchronisierter Endpoint-Katalog +belegen eine funktionsfähige Installation. Meldet ausschließlich +`BizTalk Endpoint Reachability` weiterhin `UNKNOWN`, wird die Installation +abgeschlossen und die vollständige Checkmk-Zeile als Warnung im Setup sowie +im Setup-Log ausgegeben. Der Status bleibt im Snapshot und in Checkmk +unverändert `UNKNOWN`, bis Auflösungslücken, Katalog-/Probeprobleme oder +fehlende manuelle Overrides betrieblich geklärt sind. `UNKNOWN` in Platform, +SQL oder einem anderen Kernservice bleibt ein blockierender Abnahmefehler. + Version 2.2.4 nimmt die umgeschaltete Installation zusätzlich vollständig ab: - einmaliger, triggerloser Providerlauf unter dem angegebenen Collector-Konto, @@ -54,7 +65,9 @@ Version 2.2.4 nimmt die umgeschaltete Installation zusätzlich vollständig ab: - Snapshot muss nach Beginn des Abnahmelaufs erzeugt, SHA-256-valid, für die aktuelle Maschine und vom erwarteten Collector-Konto geschrieben sein, - alle neun stabilen Services müssen eindeutig vorhanden sein; `WARN` und - `CRIT` sind reale Betriebszustände, `UNKNOWN` blockiert die Installation, + `CRIT` sind reale Betriebszustände. `UNKNOWN` in Kernservices blockiert die + Installation; ausschließlich `BizTalk Endpoint Reachability = UNKNOWN` + wird ab 2.2.6 als nicht blockierende Betriebswarnung übernommen, - bei aktivierter Endpoint-Prüfung muss auch der Katalog nach Beginn des Abnahmelaufs atomar synchronisiert worden sein. diff --git a/docs/ACC-Endpoint-Reachability-Analyse-2026-08-10.md b/docs/ACC-Endpoint-Reachability-Analyse-2026-08-10.md index 9d5ebb8..8b1aad4 100644 --- a/docs/ACC-Endpoint-Reachability-Analyse-2026-08-10.md +++ b/docs/ACC-Endpoint-Reachability-Analyse-2026-08-10.md @@ -139,7 +139,7 @@ Endpoint-/Namensfix bewusst nicht grüngefärbt. ## ACC-Abnahme nach Installation -1. Setup 2.2.5 als Update ausführen; Umgebung `ACC` kann bestehen bleiben. +1. Setup 2.2.6 als Update ausführen; Umgebung `ACC` kann bestehen bleiben. Beim Wechsel von der fehlerhaften 2.2.1-Ausgabe `BizTalk ACC ...` auf die stabilen Namen die Rename-/Discovery-Checkbox bewusst aktivieren. 2. In der installierten Config bestätigen: diff --git a/docs/BizTalk_Checkmk_Pulse_Architekturueberblick.docx b/docs/BizTalk_Checkmk_Pulse_Architekturueberblick.docx index f3849dec767bd027dc669d98b1cd9912679f70b7..a0270984dee846eb93fb5d81a62818a4dce252ff 100644 GIT binary patch delta 11361 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

T0E zgad&zkU$`E(Elm^a3ZPfd|;M;d_z7kIow>T4VMITj_l93gFqlgI1q^Pzd&ULi30`H|KI%TTa{I|K_HMB90)}GUxfVB zcmXiuzxbL3z~pevsS5&Nnt$%YKUX`IP!LRI_`f$gJP1^Q1Oj3IR~IqmAdsh%tHpaO zCntAHR}P4mgZ=-TVDs7Kt*!tB(6>T_|0njN)C58B+kYcA34+PtR#WE%!S9g9dB7kL G=zjn`(TE8E delta 11369 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 !options.ProbeEndpointConnectivity + || !string.Equals( + ExtractServiceName(x), + acceptedEndpointUnknownName, + StringComparison.Ordinal)) + .Select(ExtractServiceName) + .OrderBy(x => x, StringComparer.Ordinal) + .ToArray(); + if (blockingUnknown.Length != 0) + { + return RuntimeValidationResult.Failed( + "Snapshot enthaelt blockierende UNKNOWN-Zustaende in Kernservices. UNKNOWN=[" + + string.Join(", ", blockingUnknown) + "]."); + } + DateTime? catalogUtc = null; if (options.ProbeEndpointConnectivity) { @@ -85,14 +107,17 @@ namespace BizTalkCheckmkPulse return RuntimeValidationResult.Success( snapshot.GeneratedUtc, catalogUtc, - expectedServices.Length); + expectedServices.Length, + unknownServiceLines); } internal static bool ContainsExactStableContract( IEnumerable lines, IEnumerable expectedServices, + out string[] unknownServiceLines, out string error) { + unknownServiceLines = new string[0]; string[] actual; try { @@ -123,18 +148,11 @@ namespace BizTalkCheckmkPulse return false; } - var unknown = (lines ?? Enumerable.Empty()) + unknownServiceLines = (lines ?? Enumerable.Empty()) .Where(x => x != null && x.StartsWith("3 ", StringComparison.Ordinal)) - .Select(ExtractServiceName) - .Where(x => expected.Contains(x, StringComparer.Ordinal)) - .OrderBy(x => x, StringComparer.Ordinal) + .Where(x => expected.Contains(ExtractServiceName(x), StringComparer.Ordinal)) + .OrderBy(ExtractServiceName, StringComparer.Ordinal) .ToArray(); - if (unknown.Length != 0) - { - error = "Snapshot enthaelt unzuverlaessige UNKNOWN-Zustaende in stabilen Services. UNKNOWN=[" - + string.Join(", ", unknown) + "]."; - return false; - } error = string.Empty; return true; @@ -159,21 +177,31 @@ namespace BizTalkCheckmkPulse public DateTime GeneratedUtc { get; private set; } public DateTime? CatalogSynchronizedUtc { get; private set; } public int StableServiceCount { get; private set; } + public IReadOnlyList AcceptedUnknownLines { get; private set; } - public static RuntimeValidationResult Success(DateTime generatedUtc, DateTime? catalogUtc, int serviceCount) + public static RuntimeValidationResult Success( + DateTime generatedUtc, + DateTime? catalogUtc, + int serviceCount, + IEnumerable acceptedUnknownLines) { return new RuntimeValidationResult { IsSuccess = true, GeneratedUtc = generatedUtc, CatalogSynchronizedUtc = catalogUtc, - StableServiceCount = serviceCount + StableServiceCount = serviceCount, + AcceptedUnknownLines = (acceptedUnknownLines ?? Enumerable.Empty()).ToArray() }; } public static RuntimeValidationResult Failed(string error) { - return new RuntimeValidationResult { Error = error }; + return new RuntimeValidationResult + { + Error = error, + AcceptedUnknownLines = new string[0] + }; } } } diff --git a/tests/BizTalkCheckmkPulse.Tests/Program.cs b/tests/BizTalkCheckmkPulse.Tests/Program.cs index 55575ad..092b4ce 100644 --- a/tests/BizTalkCheckmkPulse.Tests/Program.cs +++ b/tests/BizTalkCheckmkPulse.Tests/Program.cs @@ -52,6 +52,8 @@ namespace BizTalkCheckmkPulse.Tests Run("RuntimeValidationRejectsStaleCatalog", RuntimeValidationRejectsStaleCatalog); Run("RuntimeValidationRejectsChangedServiceContract", RuntimeValidationRejectsChangedServiceContract); Run("RuntimeValidationRejectsUnknownStableService", RuntimeValidationRejectsUnknownStableService); + Run("RuntimeValidationAllowsEndpointUnknownAsWarning", RuntimeValidationAllowsEndpointUnknownAsWarning); + Run("RuntimeValidationRejectsEndpointUnknownWhenProbeDisabled", RuntimeValidationRejectsEndpointUnknownWhenProbeDisabled); Run("RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog", RuntimeValidationAllowsDisabledEndpointProbeWithoutCatalog); Run("ForceEndpointRefreshRequiresProviderMode", ForceEndpointRefreshRequiresProviderMode); Run("RuntimeValidationArgumentsAreParsedStrictly", RuntimeValidationArgumentsAreParsedStrictly); @@ -1028,8 +1030,55 @@ namespace BizTalkCheckmkPulse.Tests "BEW\\t231bizmon", true, lines); - Assert(!result.IsSuccess, "UNKNOWN stable service must fail runtime acceptance"); - Assert(result.Error.Contains("UNKNOWN=[BizTalk Platform]"), "UNKNOWN stable service reason missing"); + Assert(!result.IsSuccess, "UNKNOWN core service must fail runtime acceptance"); + Assert(result.Error.Contains("UNKNOWN=[BizTalk Platform]"), "UNKNOWN core service reason missing"); + } + + private static void RuntimeValidationAllowsEndpointUnknownAsWarning() + { + var boundary = DateTime.UtcNow.AddMinutes(-1); + var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray(); + var endpointIndex = Array.FindIndex( + lines, + x => x.Contains("\"BizTalk Endpoint Reachability\"")); + Assert(endpointIndex >= 0, "endpoint service missing from self-test contract"); + lines[endpointIndex] = "3 \"BizTalk Endpoint Reachability\" " + + "biztalk_endpoints_unresolved=1;;1;0 Endpoint reachability incomplete; unresolved=1."; + + var result = ValidateRuntimeArtifacts( + boundary, + boundary.AddSeconds(10), + boundary.AddSeconds(5), + "BEW\\t231bizmon", + "BEW\\t231bizmon", + true, + lines); + + Assert(result.IsSuccess, "endpoint UNKNOWN must be accepted as operational warning: " + result.Error); + AssertEqual(1, result.AcceptedUnknownLines.Count, "accepted endpoint UNKNOWN count"); + Assert(result.AcceptedUnknownLines[0].Contains("unresolved=1"), "endpoint UNKNOWN detail must be retained"); + } + + private static void RuntimeValidationRejectsEndpointUnknownWhenProbeDisabled() + { + var boundary = DateTime.UtcNow.AddMinutes(-1); + var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray(); + var endpointIndex = Array.FindIndex( + lines, + x => x.Contains("\"BizTalk Endpoint Reachability\"")); + lines[endpointIndex] = "3 \"BizTalk Endpoint Reachability\" - Unexpected UNKNOWN while probe is disabled."; + + var result = ValidateRuntimeArtifacts( + boundary, + boundary.AddSeconds(10), + null, + "BEW\\t231bizmon", + "BEW\\t231bizmon", + false, + lines); + + Assert(!result.IsSuccess, "endpoint UNKNOWN must be rejected when endpoint probing is disabled"); + Assert(result.Error.Contains("UNKNOWN=[BizTalk Endpoint Reachability]"), "disabled-probe UNKNOWN reason missing"); } private static void ForceEndpointRefreshRequiresProviderMode()