Harden ACC installer update and endpoint performance
This commit is contained in:
+17
-3
@@ -380,7 +380,9 @@ scripts\package-release.cmd
|
||||
Der Release-Build der Visual-Studio-2019-Solution baut Anwendung, grafischen
|
||||
Installer und Packaging-Werkzeug. Danach liegen der Paketordner
|
||||
`artifacts\BizTalkCheckmkPulse-Setup` und das direkt transportierbare
|
||||
`artifacts\BizTalkCheckmkPulse-Setup.zip` bereit.
|
||||
`artifacts\BizTalkCheckmkPulse-Setup.zip` bereit. Zusaetzlich entstehen
|
||||
`BizTalkCheckmkPulse-Setup.zip.b64.txt` fuer `certutil -decode` und eine
|
||||
SHA-256-Textdatei.
|
||||
|
||||
### 8.2 Normales Servicekonto
|
||||
|
||||
@@ -392,6 +394,14 @@ starten.
|
||||
Der Installer muss als lokaler Administrator laufen. Er vergibt keine
|
||||
AD-/BizTalk-/SQL-Rechte; diese bleiben getrennte administrative Freigaben.
|
||||
|
||||
Beim Update wird zuerst die neue Paketversion im Staging-Verzeichnis mit neun
|
||||
Self-Test-Zeilen validiert. Bestehende AppSettings werden in die neue Config
|
||||
uebernommen; neue Keys stammen aus der neuen Standardkonfiguration. Die
|
||||
Runtime-Daten unter `%ProgramData%` bleiben bestehen. Erst danach wird der
|
||||
Task gestoppt und die Programmversion umgeschaltet. Scheitert ein Folgeschritt,
|
||||
versucht der Installer Programmverzeichnis, Checkmk-Wrapper und Scheduled Task
|
||||
auf den vorherigen Stand zurueckzusetzen.
|
||||
|
||||
Der installierte Task selbst laeuft mit `RunLevel Limited`. Der Installer
|
||||
uebergibt das Kennwort direkt an die Windows-Task-Scheduler-COM-Schnittstelle;
|
||||
es wird weder in einer Prozesskommandozeile noch in Config oder Log
|
||||
@@ -542,8 +552,8 @@ BizTalk-Probes:
|
||||
| `ProbeEndpointConnectivity` | `true` |
|
||||
| `EndpointDiscoveryIntervalHours` | `168` |
|
||||
| `EndpointProbeTimeoutMilliseconds` | `3000` |
|
||||
| `EndpointProbeMaxConcurrency` | `12` |
|
||||
| `EndpointMaxCount` | `500` |
|
||||
| `EndpointProbeMaxConcurrency` | `16` |
|
||||
| `EndpointMaxCount` | `100` |
|
||||
|
||||
Alarmierung:
|
||||
|
||||
@@ -580,6 +590,10 @@ Automatisiert:
|
||||
- Endpoint-Adressen werden ohne URI-Secrets auf Host/Port reduziert,
|
||||
- manuelle Endpoint-Overrides ueberleben den automatischen Wochenabgleich,
|
||||
- nur nicht erreichbare Endpoints erscheinen im Checkmk-Detail,
|
||||
- 70 vollstaendig timeoutende eindeutige Ziele bleiben mit den Defaults in
|
||||
einem theoretischen Socket-Budget von etwa 15 Sekunden; das konfigurierte
|
||||
Maximum von 100 Zielen in etwa 21 Sekunden,
|
||||
- Update-Config-Merge erhaelt bestehende Werte und fuegt neue Defaults hinzu,
|
||||
- Snapshot-Roundtrip und Ersatz,
|
||||
- SHA-256-Manipulation wird verworfen,
|
||||
- Stale-Snapshot wird verworfen,
|
||||
|
||||
@@ -170,6 +170,8 @@ und das transportierbare ZIP:
|
||||
```text
|
||||
artifacts\
|
||||
BizTalkCheckmkPulse-Setup.zip
|
||||
BizTalkCheckmkPulse-Setup.zip.b64.txt
|
||||
BizTalkCheckmkPulse-Setup.zip.sha256.txt
|
||||
BizTalkCheckmkPulse-Setup\
|
||||
Setup.exe
|
||||
INSTALLATION.md
|
||||
@@ -183,6 +185,14 @@ Das Packaging-Projekt ist Teil von `BizTalkCheckmkPulse.sln`. Ein normaler
|
||||
`Release`-Build in Visual Studio 2019 genuegt daher; ein separates
|
||||
PowerShell-/WiX-/MSI-Tooling wird nicht benoetigt.
|
||||
|
||||
Die Base64-TXT kann auf dem Windows-Zielsystem ohne Zusatzwerkzeug dekodiert
|
||||
werden:
|
||||
|
||||
```cmd
|
||||
certutil -decode BizTalkCheckmkPulse-Setup.zip.b64.txt BizTalkCheckmkPulse-Setup.zip
|
||||
certutil -hashfile BizTalkCheckmkPulse-Setup.zip SHA256
|
||||
```
|
||||
|
||||
Format-Self-Test ohne WMI, SQL oder Event Log:
|
||||
|
||||
```cmd
|
||||
@@ -226,7 +236,7 @@ normaler Bestandteil des Kontonamens verarbeitet.
|
||||
|
||||
Wenn das Kennwort rotiert oder ablaeuft, muss es im Scheduled Task aktualisiert
|
||||
werden. Dazu `Setup.exe` mit dem neuen Kennwort erneut ausfuehren. Bis dahin
|
||||
wird der Snapshot nach 180 Sekunden stale und Checkmk zeigt alle acht Services
|
||||
wird der Snapshot nach 180 Sekunden stale und Checkmk zeigt alle neun Services
|
||||
als `UNKNOWN`.
|
||||
|
||||
Ein gMSA kann ueber die Checkbox ohne Kennworteingabe installiert werden; die
|
||||
@@ -234,15 +244,22 @@ produktive Standardbeschreibung geht vom normalen Servicekonto aus.
|
||||
|
||||
Der Installer:
|
||||
|
||||
1. kopiert EXE und Config nach
|
||||
`%ProgramFiles%\BizTalkCheckmkPulse`,
|
||||
2. erstellt `%ProgramData%\BizTalkCheckmkPulse\data` und `logs`,
|
||||
3. setzt explizite ACLs fuer Administratoren, Provider und `LocalSystem`,
|
||||
4. installiert nur den kleinen `.cmd`-Consumer unter
|
||||
1. prueft Paket und neun Self-Test-Zeilen, bevor der bestehende Task angehalten
|
||||
wird,
|
||||
2. baut die neue Version in einem Staging-Verzeichnis auf und uebernimmt
|
||||
vorhandene AppSettings; neue Keys erhalten ihre neuen Defaults. Die alten
|
||||
unveraenderten Endpoint-Defaults `12`/`500` werden auf `16`/`100` migriert,
|
||||
3. schaltet die Programmdateien per Verzeichniswechsel um; bei einem Fehler
|
||||
werden vorherige Version, Wrapper und Task best effort wiederhergestellt,
|
||||
4. behaelt `%ProgramData%\BizTalkCheckmkPulse` mit Endpoint-Katalog, Snapshot
|
||||
und Logs bei,
|
||||
5. installiert EXE und Config nach `%ProgramFiles%\BizTalkCheckmkPulse`,
|
||||
6. setzt explizite ACLs fuer Administratoren, Provider und `LocalSystem`,
|
||||
7. installiert nur den kleinen `.cmd`-Consumer unter
|
||||
`%ProgramData%\checkmk\agent\local`,
|
||||
5. registriert `BizTalk Checkmk Pulse Provider` minuetlich mit
|
||||
8. registriert `BizTalk Checkmk Pulse Provider` minuetlich mit
|
||||
`IgnoreNew`, fuenf Minuten Laufzeitlimit und zwei Wiederholungen,
|
||||
6. fuehrt den Self-Test aus und startet den Provider einmalig.
|
||||
9. fuehrt den installierten Self-Test erneut aus und startet den Provider.
|
||||
|
||||
Installation, Update, Deinstallation und Laufzeit verwenden keine PowerShell.
|
||||
|
||||
@@ -332,8 +349,8 @@ Wichtige Werte:
|
||||
| `EndpointCatalogPath` | `%ProgramData%\BizTalkCheckmkPulse\data\endpoints.xml` | Lokal gepflegte Endpoint-Konfiguration ohne vollstaendige URIs/Secrets. |
|
||||
| `EndpointDiscoveryIntervalHours` | `168` | Intervall fuer den vollstaendigen Umgebungsabgleich. |
|
||||
| `EndpointProbeTimeoutMilliseconds` | `3000` | Timeout je dedupliziertem Host/Port-Ziel. |
|
||||
| `EndpointProbeMaxConcurrency` | `12` | Begrenzte parallele Socket-Probes. |
|
||||
| `EndpointMaxCount` | `500` | Harte Obergrenze gegen fehlerhafte/uebergrosse Konfiguration. |
|
||||
| `EndpointProbeMaxConcurrency` | `16` | Begrenzte parallele Socket-Probes. |
|
||||
| `EndpointMaxCount` | `100` | Harte Obergrenze und Laufzeitbudget; bei 3 s Timeout theoretisch maximal etwa 21 s. |
|
||||
| `QueryTimeoutSeconds` | `25` | WMI-Timeout je Query. |
|
||||
| `SqlConnectionTimeoutSeconds` | `5` | SQL-Timeout je Ziel. |
|
||||
| `WarnResumableThreshold` | `1` | WARN ab n resumable Suspensions. |
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Installation auf dem BizTalk-Server
|
||||
|
||||
Falls nur die TXT-Datei uebertragen wurde:
|
||||
|
||||
```cmd
|
||||
certutil -decode BizTalkCheckmkPulse-Setup.zip.b64.txt BizTalkCheckmkPulse-Setup.zip
|
||||
certutil -hashfile BizTalkCheckmkPulse-Setup.zip SHA256
|
||||
```
|
||||
|
||||
1. `BizTalkCheckmkPulse-Setup.zip` vollstaendig in ein lokales Verzeichnis entpacken.
|
||||
2. `Setup.exe` als lokaler Administrator starten und die UAC-Abfrage bestaetigen.
|
||||
3. Das Collector-Konto im Format `DOMAIN\Benutzer` eingeben, zum Beispiel
|
||||
@@ -8,6 +15,25 @@
|
||||
5. Im Aufgabenplaner den Task `BizTalk Checkmk Pulse Provider` und danach den
|
||||
Checkmk-Agent-Dump kontrollieren.
|
||||
|
||||
Bei einer bereits vorhandenen ACC-Installation ist dies ein Update. Der
|
||||
Installer erkennt die bestehende Umgebung, behaelt Endpoint-Katalog, Snapshot
|
||||
und Logs sowie vorhandene AppSettings bei und ergaenzt neue Config-Keys aus dem
|
||||
Paket. Das BEW-Konto und sein aktuelles Kennwort muessen erneut eingegeben
|
||||
werden, weil der Scheduled Task mit den bestaetigten Zugangsdaten neu
|
||||
registriert wird.
|
||||
|
||||
Die frueheren unveraenderten Endpoint-Defaults `12` parallele Probes und
|
||||
`EndpointMaxCount=500` werden beim Update auf `16` beziehungsweise `100`
|
||||
migriert. Abweichende, bewusst konfigurierte Werte bleiben erhalten.
|
||||
|
||||
Vor dem Stoppen des vorhandenen Tasks wird die neue Version separat getestet.
|
||||
Bei normalen Servicekonten prueft der Installer ausserdem Kennwort und
|
||||
Batch-Anmelderecht vor der Umschaltung.
|
||||
Der Task wird vor dem Dateitausch deaktiviert und sein Prozessende maximal zehn
|
||||
Sekunden abgewartet, damit keine laufende Provider-EXE ueberschrieben wird.
|
||||
Bei einem Fehler nach der Umschaltung versucht der Installer, vorherige
|
||||
Programmdateien, Wrapper und Task wiederherzustellen.
|
||||
|
||||
PowerShell wird fuer Installation, Update, Deinstallation und Laufzeit nicht
|
||||
benoetigt. Das Kennwort wird direkt an die Windows-Aufgabenplanung uebergeben
|
||||
und weder in einer Datei noch in einer Prozesskommandozeile abgelegt.
|
||||
|
||||
@@ -202,11 +202,13 @@ Metriken:
|
||||
| --- | --- |
|
||||
| `biztalk_endpoints_configured` | Eintraege in `endpoints.xml`, einschliesslich derzeit inaktiver. |
|
||||
| `biztalk_endpoints_active` | Im aktuellen Lauf Started/Enabled und fuer die Probe ausgewaehlt. |
|
||||
| `biztalk_endpoints_unique_targets` | Nach Host/Port/Protokoll-Deduplizierung tatsaechlich verbundene Ziele. |
|
||||
| `biztalk_endpoints_tested` | Vollstaendig abgeschlossene Artefaktprobes. |
|
||||
| `biztalk_endpoints_available` | Erreichbare aktive Artefaktziele. |
|
||||
| `biztalk_endpoints_failed` | Nicht erreichbare aktive Artefaktziele; CRIT ab 1. |
|
||||
| `biztalk_endpoints_unsupported` | Aktive externe Adressen ohne sicher bestimmbaren Host/Port. |
|
||||
| `biztalk_endpoints_inactive_skipped` | Konfigurierte Eintraege, deren BizTalk-Artefakt aktuell nicht aktiv ist. |
|
||||
| `biztalk_endpoint_probe_duration_ms` | Gesamtlaufzeit der parallelisierten Socket-Probes in Millisekunden. |
|
||||
|
||||
Bei expliziten `udp://host:port`-Eintraegen kann ein generischer Check ohne
|
||||
applikationsspezifisches Protokoll nur DNS, Route und erfolgreichen lokalen
|
||||
|
||||
@@ -26,6 +26,22 @@ Damit wird ein inzwischen gestopptes/deaktiviertes Artefakt sofort nicht mehr
|
||||
geprueft. Neue Artefakte werden spaetestens beim naechsten Wochenabgleich
|
||||
aufgenommen.
|
||||
|
||||
## Laufzeitbudget
|
||||
|
||||
Die Socket-Probes werden nach Protokoll, Host und Port dedupliziert und mit
|
||||
maximal `EndpointProbeMaxConcurrency=16` parallelen Verbindungen ausgefuehrt.
|
||||
Bei `EndpointProbeTimeoutMilliseconds=3000` benoetigen 70 vollstaendig
|
||||
timeoutende eindeutige Ziele theoretisch etwa 15 Sekunden. Die Standardgrenze
|
||||
`EndpointMaxCount=100` begrenzt diesen Anteil eines Providerlaufs auf etwa
|
||||
21 Sekunden. Nicht pruefbare aktive BizTalk-Artefakte zaehlen nicht gegen diese
|
||||
Grenze; sie werden separat als `unsupported` gemeldet.
|
||||
|
||||
Checkmk selbst wartet nicht auf diese Verbindungen, sondern liest nur den
|
||||
zuletzt atomar geschriebenen Snapshot. Die reale ACC-Laufzeit ist nach dem
|
||||
Update an `biztalk_endpoint_probe_duration_ms` sowie an `endpoint_probe_ms` und
|
||||
`elapsed_ms` im Provider-Log zu kontrollieren. Der Scheduled Task verhindert
|
||||
mit `IgnoreNew` ueberlappende Providerlaeufe.
|
||||
|
||||
## Sicherheitsmodell
|
||||
|
||||
Der Katalog speichert nicht die vollstaendige BizTalk-Adresse. Persistiert
|
||||
|
||||
@@ -61,7 +61,7 @@ Artefakte. Nur die Textliste ist begrenzt.
|
||||
Gesund, ohne Auflistung aller Ziele:
|
||||
|
||||
```text
|
||||
0 "BizTalk ACC Endpoint Reachability" biztalk_endpoints_configured=63;;;0|biztalk_endpoints_active=61;;;0|biztalk_endpoints_tested=61;;;0|biztalk_endpoints_available=61;;;0|biztalk_endpoints_failed=0;;1;0|biztalk_endpoints_unsupported=0;;1;0|biztalk_endpoints_inactive_skipped=2;;;0 Alle 61 aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar; catalog_utc=2026-08-04T10:00:00.0000000Z
|
||||
0 "BizTalk ACC Endpoint Reachability" biztalk_endpoints_configured=63;;;0|biztalk_endpoints_active=61;;;0|biztalk_endpoints_unique_targets=44;;;0|biztalk_endpoints_tested=61;;;0|biztalk_endpoints_available=61;;;0|biztalk_endpoints_failed=0;;1;0|biztalk_endpoints_unsupported=0;;1;0|biztalk_endpoints_inactive_skipped=2;;;0|biztalk_endpoint_probe_duration_ms=842;;;0 Alle 61 aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar (unique_targets=44, probe_ms=842); catalog_utc=2026-08-04T10:00:00.0000000Z
|
||||
```
|
||||
|
||||
Fehlerhaft, nur mit nicht erreichbaren Zielen:
|
||||
|
||||
@@ -6,4 +6,6 @@ if errorlevel 1 exit /b 1
|
||||
|
||||
echo Install package written to artifacts\BizTalkCheckmkPulse-Setup
|
||||
echo Install ZIP written to artifacts\BizTalkCheckmkPulse-Setup.zip
|
||||
echo Certutil Base64 written to artifacts\BizTalkCheckmkPulse-Setup.zip.b64.txt
|
||||
echo SHA-256 written to artifacts\BizTalkCheckmkPulse-Setup.zip.sha256.txt
|
||||
exit /b 0
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace BizTalkCheckmkPulse.Packager
|
||||
{
|
||||
@@ -17,6 +19,8 @@ namespace BizTalkCheckmkPulse.Packager
|
||||
var package = Path.Combine(artifacts, "BizTalkCheckmkPulse-Setup");
|
||||
var application = Path.Combine(package, "application");
|
||||
var zip = Path.Combine(artifacts, "BizTalkCheckmkPulse-Setup.zip");
|
||||
var base64 = zip + ".b64.txt";
|
||||
var checksum = zip + ".sha256.txt";
|
||||
|
||||
if (Directory.Exists(package)) Directory.Delete(package, true);
|
||||
Directory.CreateDirectory(application);
|
||||
@@ -39,8 +43,15 @@ namespace BizTalkCheckmkPulse.Packager
|
||||
|
||||
if (File.Exists(zip)) File.Delete(zip);
|
||||
ZipFile.CreateFromDirectory(package, zip, CompressionLevel.Optimal, false);
|
||||
WriteBase64(zip, base64);
|
||||
File.WriteAllText(
|
||||
checksum,
|
||||
Sha256(zip) + " " + Path.GetFileName(zip) + Environment.NewLine,
|
||||
new UTF8Encoding(false));
|
||||
Console.WriteLine("Install package: " + package);
|
||||
Console.WriteLine("Install ZIP: " + zip);
|
||||
Console.WriteLine("Base64 TXT: " + base64);
|
||||
Console.WriteLine("SHA-256 TXT: " + checksum);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -55,5 +66,29 @@ namespace BizTalkCheckmkPulse.Packager
|
||||
if (!File.Exists(source)) throw new FileNotFoundException("Required package file missing: " + source, source);
|
||||
File.Copy(source, target, true);
|
||||
}
|
||||
|
||||
private static void WriteBase64(string source, string target)
|
||||
{
|
||||
var encoded = Convert.ToBase64String(File.ReadAllBytes(source));
|
||||
var builder = new StringBuilder(encoded.Length + encoded.Length / 64 * 2 + 2);
|
||||
for (var offset = 0; offset < encoded.Length; offset += 64)
|
||||
{
|
||||
builder.Append(encoded, offset, Math.Min(64, encoded.Length - offset));
|
||||
builder.Append('\n');
|
||||
}
|
||||
File.WriteAllText(target, builder.ToString(), new UTF8Encoding(false));
|
||||
}
|
||||
|
||||
private static string Sha256(string path)
|
||||
{
|
||||
using (var stream = File.OpenRead(path))
|
||||
using (var algorithm = SHA256.Create())
|
||||
{
|
||||
var hash = algorithm.ComputeHash(stream);
|
||||
var builder = new StringBuilder(hash.Length * 2);
|
||||
foreach (var value in hash) builder.Append(value.ToString("x2"));
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="InstallerEngine.cs" />
|
||||
<Compile Include="MainForm.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.Xml;
|
||||
@@ -27,6 +30,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
public void Install(string account, string password, bool isGmsa, string environmentName, Action<string> report)
|
||||
{
|
||||
Validate(account, password, isGmsa, environmentName);
|
||||
report = report ?? delegate { };
|
||||
var accountSid = (SecurityIdentifier)new NTAccount(account).Translate(typeof(SecurityIdentifier));
|
||||
report("Konto aufgeloest: " + account + " (" + accountSid.Value + ")");
|
||||
|
||||
@@ -37,18 +41,56 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
RequireFile(sourceExe);
|
||||
RequireFile(sourceConfig);
|
||||
RequireFile(sourceWrapper);
|
||||
|
||||
// Ein laufender Provider kann die installierte EXE waehrend eines Updates sperren.
|
||||
new TaskSchedulerService().DeleteIfExists(TaskName);
|
||||
report("Vorhandener Scheduled Task angehalten beziehungsweise fuer das Update entfernt.");
|
||||
|
||||
Directory.CreateDirectory(installDirectory);
|
||||
var targetExe = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe");
|
||||
var targetConfig = targetExe + ".config";
|
||||
File.Copy(sourceExe, targetExe, true);
|
||||
File.Copy(sourceConfig, targetConfig, true);
|
||||
SetEnvironment(targetConfig, environmentName);
|
||||
report("Programmdateien installiert: " + installDirectory);
|
||||
var stagingDirectory = installDirectory + ".staging." + Guid.NewGuid().ToString("N");
|
||||
var backupDirectory = installDirectory + ".backup." + Guid.NewGuid().ToString("N");
|
||||
var targetWrapper = Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd");
|
||||
var previousWrapper = File.Exists(targetWrapper) ? File.ReadAllBytes(targetWrapper) : null;
|
||||
var hadExistingInstallation = Directory.Exists(installDirectory);
|
||||
var taskRemoved = false;
|
||||
var backupCreated = false;
|
||||
var filesActivated = false;
|
||||
var scheduler = new TaskSchedulerService();
|
||||
|
||||
try
|
||||
{
|
||||
RunSelfTest(sourceExe);
|
||||
report("Paket-Vorpruefung erfolgreich: neun Checkmk-Services.");
|
||||
if (!isGmsa)
|
||||
{
|
||||
ValidateBatchLogon(account, password);
|
||||
report("Collector-Anmeldung und 'Log on as a batch job' vor dem Update bestaetigt.");
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(stagingDirectory);
|
||||
var stagedExe = Path.Combine(stagingDirectory, "BizTalkCheckmkPulse.exe");
|
||||
var stagedConfig = stagedExe + ".config";
|
||||
File.Copy(sourceExe, stagedExe, false);
|
||||
var effectiveEnvironment = PrepareConfig(
|
||||
sourceConfig,
|
||||
stagedConfig,
|
||||
File.Exists(targetConfig) ? targetConfig : null,
|
||||
environmentName);
|
||||
RunSelfTest(stagedExe);
|
||||
report("Update-Staging validiert. Umgebung=" + (effectiveEnvironment.Length == 0 ? "(keine)" : effectiveEnvironment) + ".");
|
||||
|
||||
// Erst nach vollstaendiger Staging-Pruefung wird der laufende Provider angehalten.
|
||||
scheduler.DeleteIfExists(TaskName);
|
||||
taskRemoved = true;
|
||||
report("Vorhandener Scheduled Task angehalten und fuer das Update entfernt.");
|
||||
|
||||
if (hadExistingInstallation)
|
||||
{
|
||||
Directory.Move(installDirectory, backupDirectory);
|
||||
backupCreated = true;
|
||||
}
|
||||
|
||||
Directory.Move(stagingDirectory, installDirectory);
|
||||
filesActivated = true;
|
||||
report(hadExistingInstallation
|
||||
? "Programmdateien atomar auf die neue Version umgestellt."
|
||||
: "Programmdateien installiert: " + installDirectory);
|
||||
|
||||
var dataDirectory = Path.Combine(runtimeDirectory, "data");
|
||||
var logDirectory = Path.Combine(runtimeDirectory, "logs");
|
||||
@@ -60,23 +102,92 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
ApplyDirectoryAcl(runtimeDirectory, accountSid, FileSystemRights.ReadAndExecute, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(dataDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.ReadAndExecute);
|
||||
ApplyDirectoryAcl(logDirectory, accountSid, FileSystemRights.Modify, FileSystemRights.Modify);
|
||||
report("Least-Privilege-Verzeichnisrechte gesetzt.");
|
||||
report("Least-Privilege-Verzeichnisrechte gesetzt; vorhandene Runtime-Daten bleiben erhalten.");
|
||||
|
||||
Directory.CreateDirectory(checkmkLocalDirectory);
|
||||
File.Copy(sourceWrapper, Path.Combine(checkmkLocalDirectory, "biztalk_checkmk_pulse.cmd"), true);
|
||||
File.Copy(sourceWrapper, targetWrapper, true);
|
||||
report("Checkmk Local Check installiert: " + checkmkLocalDirectory);
|
||||
|
||||
RunSelfTest(targetExe);
|
||||
report("Self-Test erfolgreich: acht Checkmk-Services.");
|
||||
report("Installierter Self-Test erfolgreich: neun Checkmk-Services.");
|
||||
|
||||
new TaskSchedulerService().RegisterAndStart(
|
||||
scheduler.RegisterAndStart(
|
||||
TaskName,
|
||||
targetExe,
|
||||
installDirectory,
|
||||
account,
|
||||
isGmsa ? null : password,
|
||||
isGmsa);
|
||||
report("Scheduled Task registriert: " + TaskName);
|
||||
taskRemoved = false;
|
||||
report("Scheduled Task registriert und einmalig gestartet: " + TaskName);
|
||||
|
||||
TryDeleteDirectory(backupDirectory, report);
|
||||
}
|
||||
catch (Exception installException)
|
||||
{
|
||||
var rollbackFailures = new List<string>();
|
||||
try
|
||||
{
|
||||
scheduler.DeleteIfExists(TaskName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Task stoppen: " + ex.Message);
|
||||
}
|
||||
|
||||
if (filesActivated || backupCreated)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(installDirectory)) Directory.Delete(installDirectory, true);
|
||||
if (backupCreated && Directory.Exists(backupDirectory)) Directory.Move(backupDirectory, installDirectory);
|
||||
report("Vorherige Programmversion wiederhergestellt.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Programmdateien wiederherstellen: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RestoreWrapper(targetWrapper, previousWrapper);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Checkmk-Wrapper wiederherstellen: " + ex.Message);
|
||||
}
|
||||
|
||||
if (taskRemoved && hadExistingInstallation && File.Exists(targetExe))
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler.RegisterAndStart(
|
||||
TaskName,
|
||||
targetExe,
|
||||
installDirectory,
|
||||
account,
|
||||
isGmsa ? null : password,
|
||||
isGmsa);
|
||||
report("Scheduled Task fuer die vorherige Version wiederhergestellt.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
rollbackFailures.Add("Scheduled Task wiederherstellen: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
var rollback = rollbackFailures.Count == 0
|
||||
? "Rollback erfolgreich."
|
||||
: "Rollback unvollstaendig: " + string.Join(" | ", rollbackFailures);
|
||||
throw new InvalidOperationException(
|
||||
"Installation/Update fehlgeschlagen. " + rollback + " Ursache: " + installException.Message,
|
||||
installException);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryDeleteDirectory(stagingDirectory, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Uninstall(bool keepRuntimeData, Action<string> report)
|
||||
@@ -108,14 +219,152 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
throw new FileNotFoundException("Installationspaket ist unvollstaendig. Datei fehlt: " + path, path);
|
||||
}
|
||||
|
||||
private static void SetEnvironment(string configPath, string environmentName)
|
||||
internal string GetInstalledEnvironment()
|
||||
{
|
||||
var document = new XmlDocument { PreserveWhitespace = true };
|
||||
document.Load(configPath);
|
||||
var setting = document.SelectSingleNode("/configuration/appSettings/add[@key='EnvironmentName']") as XmlElement;
|
||||
if (setting == null) throw new InvalidDataException("EnvironmentName fehlt in " + configPath + ".");
|
||||
setting.SetAttribute("value", environmentName);
|
||||
document.Save(configPath);
|
||||
var config = Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe.config");
|
||||
if (!File.Exists(config)) return string.Empty;
|
||||
try
|
||||
{
|
||||
var document = LoadXml(config);
|
||||
var setting = FindAppSetting(document, "EnvironmentName");
|
||||
return setting == null ? string.Empty : setting.GetAttribute("value").Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsInstalled
|
||||
{
|
||||
get { return File.Exists(Path.Combine(installDirectory, "BizTalkCheckmkPulse.exe")); }
|
||||
}
|
||||
|
||||
internal static string PrepareConfig(
|
||||
string sourceConfig,
|
||||
string stagedConfig,
|
||||
string existingConfig,
|
||||
string requestedEnvironment)
|
||||
{
|
||||
var document = LoadXml(sourceConfig);
|
||||
if (!string.IsNullOrWhiteSpace(existingConfig) && File.Exists(existingConfig))
|
||||
{
|
||||
var existing = LoadXml(existingConfig);
|
||||
var existingSettings = existing.SelectNodes("/configuration/appSettings/add[@key]");
|
||||
if (existingSettings != null)
|
||||
{
|
||||
foreach (XmlNode node in existingSettings)
|
||||
{
|
||||
var element = node as XmlElement;
|
||||
if (element == null) continue;
|
||||
var key = element.GetAttribute("key");
|
||||
var value = element.GetAttribute("value");
|
||||
if (IsSupersededDefault(key, value)) continue;
|
||||
var target = FindAppSetting(document, key);
|
||||
if (target != null) target.SetAttribute("value", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var environmentSetting = FindAppSetting(document, "EnvironmentName");
|
||||
if (environmentSetting == null)
|
||||
throw new InvalidDataException("EnvironmentName fehlt in " + sourceConfig + ".");
|
||||
if (!string.IsNullOrWhiteSpace(requestedEnvironment))
|
||||
environmentSetting.SetAttribute("value", requestedEnvironment.Trim());
|
||||
|
||||
document.Save(stagedConfig);
|
||||
return environmentSetting.GetAttribute("value").Trim();
|
||||
}
|
||||
|
||||
private static XmlDocument LoadXml(string path)
|
||||
{
|
||||
var document = new XmlDocument { PreserveWhitespace = true, XmlResolver = null };
|
||||
using (var reader = XmlReader.Create(path, new XmlReaderSettings
|
||||
{
|
||||
DtdProcessing = DtdProcessing.Prohibit,
|
||||
XmlResolver = null
|
||||
}))
|
||||
{
|
||||
document.Load(reader);
|
||||
}
|
||||
return document;
|
||||
}
|
||||
|
||||
private static XmlElement FindAppSetting(XmlDocument document, string key)
|
||||
{
|
||||
var nodes = document.SelectNodes("/configuration/appSettings/add[@key]");
|
||||
if (nodes == null) return null;
|
||||
foreach (XmlNode node in nodes)
|
||||
{
|
||||
var element = node as XmlElement;
|
||||
if (element != null && string.Equals(element.GetAttribute("key"), key, StringComparison.Ordinal))
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsSupersededDefault(string key, string value)
|
||||
{
|
||||
return string.Equals(key, "EndpointProbeMaxConcurrency", StringComparison.Ordinal)
|
||||
&& string.Equals(value, "12", StringComparison.Ordinal)
|
||||
|| string.Equals(key, "EndpointMaxCount", StringComparison.Ordinal)
|
||||
&& string.Equals(value, "500", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static void ValidateBatchLogon(string account, string password)
|
||||
{
|
||||
var separator = account.IndexOf('\\');
|
||||
var domain = account.Substring(0, separator);
|
||||
var user = account.Substring(separator + 1);
|
||||
IntPtr token;
|
||||
if (!LogonUser(user, domain, password, 4, 0, out token))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Collector-Anmeldung als Batch fehlgeschlagen: "
|
||||
+ new Win32Exception(Marshal.GetLastWin32Error()).Message
|
||||
+ ". Kennwort und lokales Recht 'Log on as a batch job' pruefen.");
|
||||
}
|
||||
|
||||
CloseHandle(token);
|
||||
}
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool LogonUser(
|
||||
string userName,
|
||||
string domain,
|
||||
string password,
|
||||
int logonType,
|
||||
int logonProvider,
|
||||
out IntPtr token);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr handle);
|
||||
|
||||
private static void RestoreWrapper(string path, byte[] previousContent)
|
||||
{
|
||||
if (previousContent == null)
|
||||
{
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
File.WriteAllBytes(path, previousContent);
|
||||
}
|
||||
|
||||
private static void TryDeleteDirectory(string path, Action<string> report)
|
||||
{
|
||||
if (!Directory.Exists(path)) return;
|
||||
try
|
||||
{
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (report != null) report("Hinweis: temporaeres Verzeichnis konnte nicht entfernt werden: " + path + " (" + ex.Message + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyDirectoryAcl(
|
||||
@@ -168,7 +417,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
throw new InvalidOperationException("Self-Test hat das Zeitlimit ueberschritten.");
|
||||
}
|
||||
var lines = output.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (process.ExitCode != 0 || lines.Length != 8 || lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
|
||||
if (process.ExitCode != 0 || lines.Length != 9 || lines.Any(x => !x.StartsWith("0 ", StringComparison.Ordinal)))
|
||||
throw new InvalidOperationException("Self-Test fehlgeschlagen. Exitcode=" + process.ExitCode + ", Zeilen=" + lines.Length + ". " + error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,9 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
environment.Size = new Size(160, 23);
|
||||
environment.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
environment.Items.AddRange(new object[] { "(keine)", "ACC", "DEV", "TST", "PRD" });
|
||||
environment.SelectedIndex = 0;
|
||||
var installedEnvironment = engine.GetInstalledEnvironment();
|
||||
var installedIndex = environment.Items.IndexOf(installedEnvironment);
|
||||
environment.SelectedIndex = installedIndex >= 0 ? installedIndex : 0;
|
||||
|
||||
gmsa.Location = new Point(190, 229);
|
||||
gmsa.Size = new Size(420, 24);
|
||||
@@ -81,7 +83,9 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
status.Multiline = true;
|
||||
status.ReadOnly = true;
|
||||
status.ScrollBars = ScrollBars.Vertical;
|
||||
status.Text = "Bereit. Setup.exe muss aus dem vollstaendig entpackten Installationspaket gestartet werden.";
|
||||
status.Text = engine.IsInstalled
|
||||
? "Bestehende Installation erkannt. Update behaelt Runtime-Daten und vorhandene Konfigurationswerte bei; Konto und Kennwort werden fuer den Scheduled Task neu bestaetigt."
|
||||
: "Bereit. Setup.exe muss aus dem vollstaendig entpackten Installationspaket gestartet werden.";
|
||||
|
||||
Controls.AddRange(new Control[]
|
||||
{
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("BizTalkCheckmkPulse.Tests")]
|
||||
[assembly: AssemblyTitle("BizTalk Checkmk Pulse Setup")]
|
||||
[assembly: AssemblyDescription("PowerShell-free installer and updater for BizTalk Checkmk Pulse")]
|
||||
[assembly: AssemblyCompany("BEW")]
|
||||
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
||||
[assembly: AssemblyVersion("2.2.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.1.0")]
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace BizTalkCheckmkPulse.Setup
|
||||
{
|
||||
@@ -10,6 +11,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
private const int TaskTriggerTime = 1;
|
||||
private const int TaskActionExecute = 0;
|
||||
private const int TaskInstancesIgnoreNew = 2;
|
||||
private const int TaskStateRunning = 4;
|
||||
|
||||
public void RegisterAndStart(
|
||||
string taskName,
|
||||
@@ -91,6 +93,7 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
try
|
||||
{
|
||||
task = root.GetTask(taskName);
|
||||
task.Enabled = false;
|
||||
try
|
||||
{
|
||||
task.Stop(0);
|
||||
@@ -99,6 +102,14 @@ namespace BizTalkCheckmkPulse.Setup
|
||||
{
|
||||
// Ein nicht laufender Task muss vor dem Loeschen nicht gestoppt werden.
|
||||
}
|
||||
for (var attempt = 0; attempt < 100 && (int)task.State == TaskStateRunning; attempt++)
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
if ((int)task.State == TaskStateRunning)
|
||||
{
|
||||
throw new TimeoutException("Scheduled Task konnte innerhalb von 10 Sekunden nicht beendet werden.");
|
||||
}
|
||||
root.DeleteTask(taskName, 0);
|
||||
}
|
||||
catch (COMException ex)
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
<!-- 168 Stunden = woechentlicher Abgleich mit der BizTalk-Umgebung. -->
|
||||
<add key="EndpointDiscoveryIntervalHours" value="168" />
|
||||
<add key="EndpointProbeTimeoutMilliseconds" value="3000" />
|
||||
<add key="EndpointProbeMaxConcurrency" value="12" />
|
||||
<add key="EndpointMaxCount" value="500" />
|
||||
<add key="EndpointProbeMaxConcurrency" value="16" />
|
||||
<!-- Bei 3 s Timeout und 16 parallelen Probes maximal ca. 21 s fuer 100 eindeutige Ziele. -->
|
||||
<add key="EndpointMaxCount" value="100" />
|
||||
|
||||
<add key="QueryTimeoutSeconds" value="25" />
|
||||
<!-- Testet die Anmeldung des privilegierten Provider-Kontos an den ermittelten BizTalk-Datenbanken. -->
|
||||
|
||||
@@ -391,18 +391,22 @@ namespace BizTalkCheckmkPulse
|
||||
: CheckState.Ok;
|
||||
var metrics = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_tested={2};;;0|biztalk_endpoints_available={3};;;0|biztalk_endpoints_failed={4};;1;0|biztalk_endpoints_unsupported={5};;1;0|biztalk_endpoints_inactive_skipped={6};;;0",
|
||||
"biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_unique_targets={2};;;0|biztalk_endpoints_tested={3};;;0|biztalk_endpoints_available={4};;;0|biztalk_endpoints_failed={5};;1;0|biztalk_endpoints_unsupported={6};;1;0|biztalk_endpoints_inactive_skipped={7};;;0|biztalk_endpoint_probe_duration_ms={8};;;0",
|
||||
endpointState.Configured,
|
||||
endpointState.Active,
|
||||
endpointState.UniqueTargets,
|
||||
endpointState.Results.Count,
|
||||
available,
|
||||
failed.Length,
|
||||
endpointState.UnsupportedActive,
|
||||
endpointState.SkippedInactive);
|
||||
endpointState.SkippedInactive,
|
||||
endpointState.ProbeDurationMilliseconds);
|
||||
var detail = new StringBuilder();
|
||||
if (failed.Length == 0 && !incomplete)
|
||||
{
|
||||
detail.Append("Alle ").Append(endpointState.Active).Append(" aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar");
|
||||
detail.Append("Alle ").Append(endpointState.Active).Append(" aktiven, pruefbaren Send-/Receive-Endpunkte sind erreichbar")
|
||||
.Append(" (unique_targets=").Append(endpointState.UniqueTargets)
|
||||
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds).Append(")");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -410,7 +414,9 @@ namespace BizTalkCheckmkPulse
|
||||
.Append(", tested=").Append(endpointState.Results.Count)
|
||||
.Append(", available=").Append(available)
|
||||
.Append(", failed=").Append(failed.Length)
|
||||
.Append(", unsupported_external=").Append(endpointState.UnsupportedActive);
|
||||
.Append(", unsupported_external=").Append(endpointState.UnsupportedActive)
|
||||
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
|
||||
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds);
|
||||
AppendLimitedList(detail, "unavailable", failed.Select(x =>
|
||||
EndpointConnectivityProbe.Display(x.Endpoint) + "(" + EmptyAsUnknown(x.Failure) + ")"));
|
||||
if (!string.IsNullOrWhiteSpace(endpointState.Failure))
|
||||
|
||||
@@ -89,8 +89,8 @@ namespace BizTalkCheckmkPulse
|
||||
MachineName = machine,
|
||||
EnvironmentName = catalogEnvironment,
|
||||
SynchronizedUtc = synchronizedUtc,
|
||||
ActiveCandidates = ReadInt(root, "activeCandidates", 0, _maxEntries),
|
||||
UnsupportedCandidates = ReadInt(root, "unsupportedCandidates", 0, _maxEntries)
|
||||
ActiveCandidates = ReadInt(root, "activeCandidates", 0, 100000),
|
||||
UnsupportedCandidates = ReadInt(root, "unsupportedCandidates", 0, 100000)
|
||||
};
|
||||
|
||||
foreach (var node in root.Elements("Endpoint"))
|
||||
|
||||
@@ -120,7 +120,15 @@ namespace BizTalkCheckmkPulse
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var probeResult in ProbeAllAsync(activeEntries).GetAwaiter().GetResult())
|
||||
var probeStopwatch = Stopwatch.StartNew();
|
||||
var probeResults = ProbeAllAsync(activeEntries).GetAwaiter().GetResult();
|
||||
probeStopwatch.Stop();
|
||||
state.ProbeDurationMilliseconds = probeStopwatch.ElapsedMilliseconds;
|
||||
state.UniqueTargets = activeEntries
|
||||
.Select(TargetKey)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Count();
|
||||
foreach (var probeResult in probeResults)
|
||||
{
|
||||
state.Results.Add(probeResult);
|
||||
if (!probeResult.Available)
|
||||
@@ -192,10 +200,9 @@ namespace BizTalkCheckmkPulse
|
||||
{
|
||||
var catalog = SynchronizeCatalog(existing, candidates, synchronizedUtc);
|
||||
catalog.EnvironmentName = _options.EnvironmentName ?? string.Empty;
|
||||
if (catalog.Entries.Count > _options.EndpointMaxCount
|
||||
|| catalog.ActiveCandidates > _options.EndpointMaxCount)
|
||||
if (catalog.Entries.Count > _options.EndpointMaxCount)
|
||||
{
|
||||
throw new InvalidDataException("Discovered endpoint candidates exceed EndpointMaxCount.");
|
||||
throw new InvalidDataException("Probeable endpoints exceed EndpointMaxCount.");
|
||||
}
|
||||
|
||||
return catalog;
|
||||
@@ -221,7 +228,7 @@ namespace BizTalkCheckmkPulse
|
||||
private async Task<IReadOnlyCollection<EndpointProbeResult>> ProbeAllAsync(EndpointCatalogEntry[] endpoints)
|
||||
{
|
||||
var unique = endpoints
|
||||
.GroupBy(x => x.Protocol.ToUpperInvariant() + "|" + x.Host.ToUpperInvariant() + "|" + x.Port)
|
||||
.GroupBy(TargetKey, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var outcomes = new Dictionary<string, NetworkOutcome>(StringComparer.OrdinalIgnoreCase);
|
||||
using (var gate = new SemaphoreSlim(_options.EndpointProbeMaxConcurrency))
|
||||
@@ -248,7 +255,7 @@ namespace BizTalkCheckmkPulse
|
||||
|
||||
return endpoints.Select(endpoint =>
|
||||
{
|
||||
var key = endpoint.Protocol.ToUpperInvariant() + "|" + endpoint.Host.ToUpperInvariant() + "|" + endpoint.Port;
|
||||
var key = TargetKey(endpoint);
|
||||
var outcome = outcomes[key];
|
||||
return new EndpointProbeResult
|
||||
{
|
||||
@@ -260,6 +267,28 @@ namespace BizTalkCheckmkPulse
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
internal static long CalculateWorstCaseProbeMilliseconds(int targetCount, int concurrency, int timeoutMilliseconds)
|
||||
{
|
||||
if (targetCount <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (concurrency <= 0 || timeoutMilliseconds <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("concurrency");
|
||||
}
|
||||
|
||||
return ((targetCount + concurrency - 1L) / concurrency) * timeoutMilliseconds;
|
||||
}
|
||||
|
||||
private static string TargetKey(EndpointCatalogEntry endpoint)
|
||||
{
|
||||
return endpoint.Protocol.ToUpperInvariant()
|
||||
+ "|" + endpoint.Host.ToUpperInvariant()
|
||||
+ "|" + endpoint.Port;
|
||||
}
|
||||
|
||||
private async Task<NetworkOutcome> ProbeOneAsync(EndpointCatalogEntry endpoint)
|
||||
{
|
||||
return string.Equals(endpoint.Protocol, "UDP", StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
@@ -349,6 +349,8 @@ namespace BizTalkCheckmkPulse
|
||||
public int Active { get; set; }
|
||||
public int SkippedInactive { get; set; }
|
||||
public int UnsupportedActive { get; set; }
|
||||
public int UniqueTargets { get; set; }
|
||||
public long ProbeDurationMilliseconds { get; set; }
|
||||
public string Failure { get; set; }
|
||||
public List<EndpointProbeResult> Results { get; private set; }
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ namespace BizTalkCheckmkPulse
|
||||
EndpointCatalogMaxBytes = 1048576;
|
||||
EndpointDiscoveryIntervalHours = 168;
|
||||
EndpointProbeTimeoutMilliseconds = 3000;
|
||||
EndpointProbeMaxConcurrency = 12;
|
||||
EndpointMaxCount = 500;
|
||||
EndpointProbeMaxConcurrency = 16;
|
||||
EndpointMaxCount = 100;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -102,6 +102,10 @@ namespace BizTalkCheckmkPulse
|
||||
+ result.EndpointConnectivity.Active
|
||||
+ " endpoints_failed="
|
||||
+ result.EndpointConnectivity.Results.Count(x => !x.Available)
|
||||
+ " endpoints_unique_targets="
|
||||
+ result.EndpointConnectivity.UniqueTargets
|
||||
+ " endpoint_probe_ms="
|
||||
+ result.EndpointConnectivity.ProbeDurationMilliseconds
|
||||
+ " elapsed_ms="
|
||||
+ stopwatch.ElapsedMilliseconds);
|
||||
return 0;
|
||||
|
||||
@@ -6,5 +6,5 @@ using System.Reflection;
|
||||
[assembly: AssemblyDescription("Privileged BizTalk data provider and validated Checkmk snapshot consumer")]
|
||||
[assembly: AssemblyCompany("BEW")]
|
||||
[assembly: AssemblyProduct("BizTalk Checkmk Pulse")]
|
||||
[assembly: AssemblyVersion("2.2.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.0.0")]
|
||||
[assembly: AssemblyVersion("2.2.1.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.1.0")]
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
<Project>{A4D4D050-9EA7-4A71-B510-7D9D699B9F38}</Project>
|
||||
<Name>BizTalkCheckmkPulse</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\src\BizTalkCheckmkPulse.Setup\BizTalkCheckmkPulse.Setup.csproj">
|
||||
<Project>{764AC43A-26D8-43C1-9121-13FC8A9CC8B1}</Project>
|
||||
<Name>BizTalkCheckmkPulse.Setup</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
Run("EndpointCatalogPreservesManualOverrides", EndpointCatalogPreservesManualOverrides);
|
||||
Run("EndpointCatalogRoundTrip", EndpointCatalogRoundTrip);
|
||||
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
|
||||
Run("EndpointProbeBudgetFitsMinuteInterval", EndpointProbeBudgetFitsMinuteInterval);
|
||||
Run("InstallerUpdatePreservesExistingSettings", InstallerUpdatePreservesExistingSettings);
|
||||
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
|
||||
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
|
||||
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
|
||||
@@ -454,11 +456,11 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
{
|
||||
EnvironmentName = "ACC",
|
||||
SynchronizedUtc = DateTime.UtcNow,
|
||||
ActiveCandidates = 1,
|
||||
ActiveCandidates = 150,
|
||||
UnsupportedCandidates = 0
|
||||
};
|
||||
catalog.Entries.Add(TestEndpoint("Orders", "api.example.test"));
|
||||
var store = new EndpointCatalogStore(path, 1048576, 500);
|
||||
var store = new EndpointCatalogStore(path, 1048576, 100);
|
||||
store.Write(catalog);
|
||||
var loaded = store.Read("ACC");
|
||||
|
||||
@@ -493,6 +495,75 @@ namespace BizTalkCheckmkPulse.Tests
|
||||
};
|
||||
}
|
||||
|
||||
private static void EndpointProbeBudgetFitsMinuteInterval()
|
||||
{
|
||||
var options = new MonitoringOptions();
|
||||
var seventyTargets = EndpointConnectivityProbe.CalculateWorstCaseProbeMilliseconds(
|
||||
70,
|
||||
options.EndpointProbeMaxConcurrency,
|
||||
options.EndpointProbeTimeoutMilliseconds);
|
||||
var configuredMaximum = EndpointConnectivityProbe.CalculateWorstCaseProbeMilliseconds(
|
||||
options.EndpointMaxCount,
|
||||
options.EndpointProbeMaxConcurrency,
|
||||
options.EndpointProbeTimeoutMilliseconds);
|
||||
|
||||
AssertEqual(15000, (int)seventyTargets, "70-target theoretical timeout budget");
|
||||
Assert(configuredMaximum <= 30000, "configured endpoint worst-case must leave headroom in the minute interval");
|
||||
}
|
||||
|
||||
private static void InstallerUpdatePreservesExistingSettings()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.InstallerTests." + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(directory);
|
||||
try
|
||||
{
|
||||
var source = Path.Combine(directory, "source.config");
|
||||
var existing = Path.Combine(directory, "existing.config");
|
||||
var staged = Path.Combine(directory, "staged.config");
|
||||
File.WriteAllText(
|
||||
source,
|
||||
"<configuration><appSettings>"
|
||||
+ "<add key=\"EnvironmentName\" value=\"\" />"
|
||||
+ "<add key=\"EndpointProbeMaxConcurrency\" value=\"16\" />"
|
||||
+ "<add key=\"EndpointMaxCount\" value=\"100\" />"
|
||||
+ "<add key=\"NewSetting\" value=\"new-default\" />"
|
||||
+ "</appSettings></configuration>",
|
||||
new UTF8Encoding(false));
|
||||
File.WriteAllText(
|
||||
existing,
|
||||
"<configuration><appSettings>"
|
||||
+ "<add key=\"EnvironmentName\" value=\"ACC\" />"
|
||||
+ "<add key=\"EndpointProbeMaxConcurrency\" value=\"7\" />"
|
||||
+ "<add key=\"EndpointMaxCount\" value=\"500\" />"
|
||||
+ "<add key=\"RemovedLegacySetting\" value=\"legacy\" />"
|
||||
+ "</appSettings></configuration>",
|
||||
new UTF8Encoding(false));
|
||||
|
||||
var effectiveEnvironment = BizTalkCheckmkPulse.Setup.InstallerEngine.PrepareConfig(
|
||||
source,
|
||||
staged,
|
||||
existing,
|
||||
string.Empty);
|
||||
var merged = File.ReadAllText(staged);
|
||||
AssertEqual("ACC", effectiveEnvironment, "preserved installer environment");
|
||||
Assert(merged.Contains("key=\"EndpointProbeMaxConcurrency\" value=\"7\""), "existing operational value must be preserved");
|
||||
Assert(merged.Contains("key=\"EndpointMaxCount\" value=\"100\""), "superseded old default must migrate to new bounded default");
|
||||
Assert(merged.Contains("key=\"NewSetting\" value=\"new-default\""), "new source setting must be added");
|
||||
Assert(merged.IndexOf("RemovedLegacySetting", StringComparison.Ordinal) < 0, "removed legacy key must not be resurrected");
|
||||
|
||||
effectiveEnvironment = BizTalkCheckmkPulse.Setup.InstallerEngine.PrepareConfig(
|
||||
source,
|
||||
staged,
|
||||
existing,
|
||||
"PRD");
|
||||
AssertEqual("PRD", effectiveEnvironment, "requested environment override");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(directory)) Directory.Delete(directory, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SnapshotRejectsTampering()
|
||||
{
|
||||
WithTemporarySnapshot((path, store) =>
|
||||
|
||||
Reference in New Issue
Block a user