Harden RTM endpoint adapter handling
This commit is contained in:
@@ -357,6 +357,8 @@ namespace BizTalkCheckmkPulse
|
||||
/// <summary>
|
||||
/// Formatiert die Netzwerk-Erreichbarkeit aller aktuell aktiven, pruefbaren BizTalk-Endpunkte.
|
||||
/// </summary>
|
||||
/// <param name="result">Vollstaendiges Provider-Ergebnis mit Endpoint-Zustand.</param>
|
||||
/// <returns>Eine syntaktisch gueltige Checkmk-Local-Check-Zeile.</returns>
|
||||
private string FormatEndpointConnectivity(ProbeResult result)
|
||||
{
|
||||
var endpointState = result.EndpointConnectivity;
|
||||
@@ -378,7 +380,12 @@ namespace BizTalkCheckmkPulse
|
||||
"Endpoint-Pruefung nicht verlaesslich moeglich. " + EmptyAsUnknown(endpointState.Failure));
|
||||
}
|
||||
|
||||
var failed = endpointState.Results.Where(x => !x.Available).ToArray();
|
||||
var failed = endpointState.Results
|
||||
.Where(x => !x.Available && !x.Endpoint.BestEffort)
|
||||
.ToArray();
|
||||
var ignoredBestEffort = endpointState.Results
|
||||
.Where(x => !x.Available && x.Endpoint.BestEffort)
|
||||
.ToArray();
|
||||
var available = endpointState.Results.Count(x => x.Available);
|
||||
var incomplete = endpointState.Results.Count != endpointState.Active
|
||||
|| endpointState.UnresolvedActive > 0
|
||||
@@ -391,7 +398,7 @@ namespace BizTalkCheckmkPulse
|
||||
: CheckState.Ok;
|
||||
var metrics = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_unique_targets={2};;;0|biztalk_endpoints_tested={3};;;0|biztalk_endpoints_available={4};;;0|biztalk_endpoints_failed={5};;1;0|biztalk_endpoints_unresolved={6};;1;0|biztalk_endpoints_expected_non_socket={7};;;0|biztalk_endpoints_manual_overrides={8};;;0|biztalk_endpoints_inactive_skipped={9};;;0|biztalk_endpoint_probe_duration_ms={10};;;0",
|
||||
"biztalk_endpoints_configured={0};;;0|biztalk_endpoints_active={1};;;0|biztalk_endpoints_unique_targets={2};;;0|biztalk_endpoints_tested={3};;;0|biztalk_endpoints_available={4};;;0|biztalk_endpoints_failed={5};;1;0|biztalk_endpoints_unresolved={6};;1;0|biztalk_endpoints_expected_non_socket={7};;;0|biztalk_endpoints_best_effort={8};;;0|biztalk_endpoints_best_effort_ignored={9};;;0|biztalk_endpoints_manual_overrides={10};;;0|biztalk_endpoints_inactive_skipped={11};;;0|biztalk_endpoint_probe_duration_ms={12};;;0",
|
||||
endpointState.Configured,
|
||||
endpointState.Active,
|
||||
endpointState.UniqueTargets,
|
||||
@@ -400,15 +407,21 @@ namespace BizTalkCheckmkPulse
|
||||
failed.Length,
|
||||
endpointState.UnresolvedActive,
|
||||
endpointState.ExpectedNonProbeableActive,
|
||||
endpointState.BestEffortActive,
|
||||
ignoredBestEffort.Length,
|
||||
endpointState.ManualOverridesActive,
|
||||
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")
|
||||
.Append(" (unique_targets=").Append(endpointState.UniqueTargets)
|
||||
detail.Append("Alle regulaer alarmierenden aktiven Send-/Receive-Endpunkte sind erreichbar")
|
||||
.Append(" (active=").Append(endpointState.Active)
|
||||
.Append(", available=").Append(available)
|
||||
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
|
||||
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
|
||||
.Append(", best_effort=").Append(endpointState.BestEffortActive)
|
||||
.Append(", best_effort_ignored=").Append(ignoredBestEffort.Length)
|
||||
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
|
||||
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds).Append(")");
|
||||
}
|
||||
@@ -420,6 +433,8 @@ namespace BizTalkCheckmkPulse
|
||||
.Append(", failed=").Append(failed.Length)
|
||||
.Append(", unresolved=").Append(endpointState.UnresolvedActive)
|
||||
.Append(", expected_non_socket=").Append(endpointState.ExpectedNonProbeableActive)
|
||||
.Append(", best_effort=").Append(endpointState.BestEffortActive)
|
||||
.Append(", best_effort_ignored=").Append(ignoredBestEffort.Length)
|
||||
.Append(", manual_overrides=").Append(endpointState.ManualOverridesActive)
|
||||
.Append(", unique_targets=").Append(endpointState.UniqueTargets)
|
||||
.Append(", probe_ms=").Append(endpointState.ProbeDurationMilliseconds);
|
||||
|
||||
@@ -13,9 +13,17 @@ namespace BizTalkCheckmkPulse
|
||||
@"^(?<host>\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):(?<port>\d{1,5})(?:[/\\].*)?$",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
private static readonly Regex DatabaseProviderPattern = new Regex(
|
||||
@"/(?:Oracle|SQLServer)(?:[/;?]|$)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Aktiver BizTalk-Transport mit Adaptername und Adresse.</param>
|
||||
/// <param name="entry">Geheimnisfreier Katalogeintrag, sofern ein Ziel bestimmt wurde.</param>
|
||||
/// <param name="reason">Begruendung, wenn kein pruefbares Ziel bestimmt wurde.</param>
|
||||
/// <returns><see langword="true"/>, wenn Host, Port und Protokoll sicher bestimmt wurden.</returns>
|
||||
public static bool TryCreate(
|
||||
EndpointCandidate candidate,
|
||||
out EndpointCatalogEntry entry,
|
||||
@@ -30,6 +38,8 @@ namespace BizTalkCheckmkPulse
|
||||
/// <summary>
|
||||
/// Trennt sicher pruefbare Ziele von erwartbar lokalen/adapterinternen Adressen und echten Aufloesungsluecken.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Zu klassifizierender BizTalk-Transport.</param>
|
||||
/// <returns>Klassifizierung sowie gegebenenfalls das pruefbare Socket-Ziel.</returns>
|
||||
public static EndpointResolution Analyze(EndpointCandidate candidate)
|
||||
{
|
||||
if (candidate == null)
|
||||
@@ -42,6 +52,13 @@ namespace BizTalkCheckmkPulse
|
||||
return Expected("Dynamischer Send Port besitzt kein statisch pruefbares Ziel.");
|
||||
}
|
||||
|
||||
// Scheduling-Adapter bilden zeitliche BizTalk-Steuerung ab und besitzen kein
|
||||
// fachlich relevantes Remoteziel. Sie werden bewusst vollstaendig ausgeklammert.
|
||||
if (IsScheduler(candidate))
|
||||
{
|
||||
return Expected("Scheduling-Komponente ist von der Endpoint-Pruefung ausgenommen.");
|
||||
}
|
||||
|
||||
var address = (candidate.Address ?? string.Empty).Trim();
|
||||
if (address.Length == 0)
|
||||
{
|
||||
@@ -80,6 +97,33 @@ namespace BizTalkCheckmkPulse
|
||||
return Expected("Relative Receive-Listener-Adresse enthaelt keine eindeutige Site-Bindung.");
|
||||
}
|
||||
|
||||
// DATABASE und WCF-SAP liefern Adapteradressen, deren Socket-Ziel nicht von
|
||||
// System.Uri abgeleitet werden kann. Eine fehlende sichere Ableitung ist bei
|
||||
// diesen Best-Effort-Adaptern kein Abdeckungsfehler.
|
||||
if (IsDatabaseAdapter(candidate))
|
||||
{
|
||||
EndpointCatalogEntry databaseEntry;
|
||||
string databaseReason;
|
||||
if (TryResolveDatabase(candidate, address, out databaseEntry, out databaseReason))
|
||||
{
|
||||
return Probeable(databaseEntry);
|
||||
}
|
||||
|
||||
return Expected(databaseReason);
|
||||
}
|
||||
|
||||
if (IsWcfSapAdapter(candidate))
|
||||
{
|
||||
EndpointCatalogEntry sapEntry;
|
||||
string sapReason;
|
||||
if (TryResolveWcfSap(candidate, address, out sapEntry, out sapReason))
|
||||
{
|
||||
return Probeable(sapEntry);
|
||||
}
|
||||
|
||||
return Expected(sapReason);
|
||||
}
|
||||
|
||||
EndpointCatalogEntry entry;
|
||||
string protocol;
|
||||
string host;
|
||||
@@ -115,8 +159,306 @@ namespace BizTalkCheckmkPulse
|
||||
Host = host,
|
||||
Port = port,
|
||||
Enabled = true,
|
||||
AutoDiscovered = true
|
||||
AutoDiscovered = true,
|
||||
BestEffort = false
|
||||
};
|
||||
return Probeable(entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Zu klassifizierender BizTalk-Transport.</param>
|
||||
/// <returns><see langword="true"/> nur bei einer echten Aufloesungsluecke.</returns>
|
||||
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
|
||||
{
|
||||
return Analyze(candidate).Status == EndpointResolutionStatus.Unresolved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Leitet das Oracle-TCP-Ziel aus der expliziten DATABASE-Adresse ab.
|
||||
/// </summary>
|
||||
/// <param name="candidate">BizTalk-Artefakt, aus dem der Katalogeintrag aufgebaut wird.</param>
|
||||
/// <param name="address">Vollstaendige DATABASE-Transportadresse.</param>
|
||||
/// <param name="entry">Abgeleiteter geheimnisfreier Best-Effort-Eintrag.</param>
|
||||
/// <param name="reason">Begruendung, wenn Host oder Anbieter nicht sicher erkennbar ist.</param>
|
||||
/// <returns><see langword="true"/>, wenn ein Oracle-Host und TCP-Port bestimmt wurden.</returns>
|
||||
private static bool TryResolveDatabase(
|
||||
EndpointCandidate candidate,
|
||||
string address,
|
||||
out EndpointCatalogEntry entry,
|
||||
out string reason)
|
||||
{
|
||||
entry = null;
|
||||
reason = "DATABASE-Adresse enthaelt keinen sicher bestimmbaren Oracle-Listener.";
|
||||
if (!address.StartsWith("database://", StringComparison.OrdinalIgnoreCase)
|
||||
|| !DatabaseProviderPattern.IsMatch(address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
if (!Uri.TryCreate(address, UriKind.Absolute, out uri)
|
||||
|| string.IsNullOrWhiteSpace(uri.Host))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var oracle = Regex.IsMatch(
|
||||
address,
|
||||
@"/Oracle(?:[/;?]|$)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
if (!oracle)
|
||||
{
|
||||
reason = "Nicht-Oracle-DATABASE-Ziel wird ohne explizite Betriebsfreigabe nicht automatisch geprueft.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var port = HasExplicitPort(address) ? uri.Port : 1521;
|
||||
string validationReason;
|
||||
var host = NormalizeHost(uri.Host);
|
||||
if (!Validate(host, port, out validationReason))
|
||||
{
|
||||
reason = validationReason;
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = CreateAdapterEntry(candidate, host, port);
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Leitet den ersten erreichbaren TCP-Hop einer WCF-SAP-Verbindung ab.
|
||||
/// </summary>
|
||||
/// <param name="candidate">BizTalk-Artefakt, aus dem der Katalogeintrag aufgebaut wird.</param>
|
||||
/// <param name="address">Vollstaendige SAP-Verbindungs-URI.</param>
|
||||
/// <param name="entry">Abgeleiteter geheimnisfreier Best-Effort-Eintrag.</param>
|
||||
/// <param name="reason">Begruendung, wenn Gateway oder SAProuter nicht sicher bestimmbar ist.</param>
|
||||
/// <returns><see langword="true"/>, wenn ein SAProuter- oder Gateway-Ziel bestimmt wurde.</returns>
|
||||
private static bool TryResolveWcfSap(
|
||||
EndpointCandidate candidate,
|
||||
string address,
|
||||
out EndpointCatalogEntry entry,
|
||||
out string reason)
|
||||
{
|
||||
entry = null;
|
||||
reason = "WCF-SAP-Adresse enthaelt keinen sicher bestimmbaren Gateway- oder SAProuter-Port.";
|
||||
var decoded = DecodeAdapterAddress(address);
|
||||
|
||||
string route;
|
||||
if (TryReadParameter(decoded, "SAPROUTER", out route))
|
||||
{
|
||||
string routerHost;
|
||||
int routerPort;
|
||||
if (TryParseSapRouter(route, out routerHost, out routerPort))
|
||||
{
|
||||
entry = CreateAdapterEntry(candidate, routerHost, routerPort);
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
string gatewayHost;
|
||||
string gatewayService;
|
||||
if ((!TryReadParameter(decoded, "ListenerGwHost", out gatewayHost)
|
||||
|| !TryReadParameter(decoded, "ListenerGwServ", out gatewayService))
|
||||
&& (!TryReadParameter(decoded, "GwHost", out gatewayHost)
|
||||
|| !TryReadParameter(decoded, "GwServ", out gatewayService)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int gatewayPort;
|
||||
if (!TryResolveSapService(gatewayService, out gatewayPort))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gatewayHost = NormalizeHost(gatewayHost);
|
||||
string validationReason;
|
||||
if (!Validate(gatewayHost, gatewayPort, out validationReason))
|
||||
{
|
||||
reason = validationReason;
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = CreateAdapterEntry(candidate, gatewayHost, gatewayPort);
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Baut einen TCP-Katalogeintrag fuer einen automatisch abgeleiteten Best-Effort-Adapter.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Quellartefakt fuer Metadaten und stabilen Schluessel.</param>
|
||||
/// <param name="host">Bereinigter DNS-Name oder IP-Adresse.</param>
|
||||
/// <param name="port">Gueltiger TCP-Zielport.</param>
|
||||
/// <returns>Automatisch verwalteter, nicht alarmierender Katalogeintrag.</returns>
|
||||
private static EndpointCatalogEntry CreateAdapterEntry(EndpointCandidate candidate, string host, int port)
|
||||
{
|
||||
return new EndpointCatalogEntry
|
||||
{
|
||||
Key = candidate.Key,
|
||||
ArtifactType = candidate.ArtifactType,
|
||||
ApplicationName = candidate.ApplicationName,
|
||||
ArtifactName = candidate.ArtifactName,
|
||||
TransportRole = candidate.TransportRole,
|
||||
AdapterName = candidate.AdapterName,
|
||||
Protocol = "TCP",
|
||||
Host = host,
|
||||
Port = port,
|
||||
Enabled = true,
|
||||
AutoDiscovered = true,
|
||||
BestEffort = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest einen URI-Parameter auch aus SAP-URIs mit wiederholtem Fragezeichen.
|
||||
/// </summary>
|
||||
/// <param name="address">Dekodierte SAP-Verbindungsadresse.</param>
|
||||
/// <param name="name">Name des gesuchten Query-Parameters.</param>
|
||||
/// <param name="value">Bereinigter Parameterwert.</param>
|
||||
/// <returns><see langword="true"/>, wenn ein nicht leerer Wert gefunden wurde.</returns>
|
||||
private static bool TryReadParameter(string address, string name, out string value)
|
||||
{
|
||||
value = string.Empty;
|
||||
var match = Regex.Match(
|
||||
address ?? string.Empty,
|
||||
@"(?:^|[?&])" + Regex.Escape(name) + @"=(?<value>[^?&]+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = match.Groups["value"].Value.Trim();
|
||||
return value.Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dekodiert URI-Escapes, ohne eine fehlerhafte Adapteradresse zum Laufzeitfehler zu machen.
|
||||
/// </summary>
|
||||
/// <param name="address">Rohe BizTalk-Transportadresse.</param>
|
||||
/// <returns>Bestmoeglich dekodierte Adresse.</returns>
|
||||
private static string DecodeAdapterAddress(string address)
|
||||
{
|
||||
var normalized = (address ?? string.Empty).Replace("&", "&");
|
||||
try
|
||||
{
|
||||
return Uri.UnescapeDataString(normalized);
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bestimmt Host und Port des ersten SAProuter-Hops einer Route.
|
||||
/// </summary>
|
||||
/// <param name="route">SAProuter-Route im Format <c>/H/host/S/port</c>.</param>
|
||||
/// <param name="host">Host des ersten Router-Hops.</param>
|
||||
/// <param name="port">Expliziter Port oder SAProuter-Standardport 3299.</param>
|
||||
/// <returns><see langword="true"/>, wenn der erste Hop gueltig ist.</returns>
|
||||
private static bool TryParseSapRouter(string route, out string host, out int port)
|
||||
{
|
||||
host = string.Empty;
|
||||
port = 0;
|
||||
var match = Regex.Match(
|
||||
route ?? string.Empty,
|
||||
@"/H/(?<host>[^/]+)(?:/S/(?<service>[^/]+))?",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
host = NormalizeHost(match.Groups["host"].Value);
|
||||
port = 3299;
|
||||
var service = match.Groups["service"].Value;
|
||||
if (service.Length > 0 && !TryResolveSapService(service, out port))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string reason;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uebersetzt numerische SAP-Dienste und sapgwNN in einen TCP-Port.
|
||||
/// </summary>
|
||||
/// <param name="service">Numerischer Port oder SAP-Gateway-Dienstname.</param>
|
||||
/// <param name="port">Aufgeloester TCP-Port.</param>
|
||||
/// <returns><see langword="true"/> bei einem gueltigen Port oder <c>sapgwNN</c>.</returns>
|
||||
private static bool TryResolveSapService(string service, out int port)
|
||||
{
|
||||
port = 0;
|
||||
var normalized = (service ?? string.Empty).Trim();
|
||||
if (int.TryParse(normalized, out port))
|
||||
{
|
||||
return port >= 1 && port <= 65535;
|
||||
}
|
||||
|
||||
var match = Regex.Match(
|
||||
normalized,
|
||||
@"^sapgw(?<instance>\d{2})$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
int instance;
|
||||
if (!match.Success || !int.TryParse(match.Groups["instance"].Value, out instance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
port = 3300 + instance;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erkennt BizTalk-Scheduling-Komponenten, die kein Remoteziel darstellen.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
|
||||
/// <returns><see langword="true"/> fuer Schedule-/Scheduler-Konfigurationen.</returns>
|
||||
private static bool IsScheduler(EndpointCandidate candidate)
|
||||
{
|
||||
return Contains(candidate.AdapterName, "SCHEDUL")
|
||||
|| (candidate.Address ?? string.Empty).StartsWith("schedule", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erkennt den kundenspezifischen DATABASE-Adapter und dessen URI-Schema.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
|
||||
/// <returns><see langword="true"/> fuer DATABASE-Konfigurationen.</returns>
|
||||
private static bool IsDatabaseAdapter(EndpointCandidate candidate)
|
||||
{
|
||||
return Contains(candidate.AdapterName, "DATABASE")
|
||||
|| (candidate.Address ?? string.Empty).StartsWith("database://", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erkennt WCF-SAP-Adapter, ohne beliebige Adapter mit SAP im Namen einzubeziehen.
|
||||
/// </summary>
|
||||
/// <param name="candidate">Zu pruefender BizTalk-Transport.</param>
|
||||
/// <returns><see langword="true"/> fuer eindeutig erkannte WCF-SAP-Konfigurationen.</returns>
|
||||
private static bool IsWcfSapAdapter(EndpointCandidate candidate)
|
||||
{
|
||||
return Contains(candidate.AdapterName, "WCF-SAP")
|
||||
|| Contains(candidate.AdapterName, "WCF SAP")
|
||||
// BizTalk meldet den SAP Binding-Typ in manchen Umgebungen nur als
|
||||
// WCF-Custom; das eindeutige sap://-Schema bleibt dennoch erhalten.
|
||||
|| (candidate.Address ?? string.Empty).StartsWith("sap://", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verpackt einen gueltigen Katalogeintrag als pruefbare Aufloesung.
|
||||
/// </summary>
|
||||
/// <param name="entry">Vollstaendig validierter Katalogeintrag.</param>
|
||||
/// <returns>Pruefbare Endpoint-Aufloesung ohne Fehlergrund.</returns>
|
||||
private static EndpointResolution Probeable(EndpointCatalogEntry entry)
|
||||
{
|
||||
return new EndpointResolution
|
||||
{
|
||||
Status = EndpointResolutionStatus.Probeable,
|
||||
@@ -125,14 +467,6 @@ namespace BizTalkCheckmkPulse
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
|
||||
/// </summary>
|
||||
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
|
||||
{
|
||||
return Analyze(candidate).Status == EndpointResolutionStatus.Unresolved;
|
||||
}
|
||||
|
||||
private static bool TryResolve(
|
||||
string address,
|
||||
string adapterName,
|
||||
|
||||
@@ -20,6 +20,12 @@ namespace BizTalkCheckmkPulse
|
||||
private readonly int _maxBytes;
|
||||
private readonly int _maxEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Initialisiert den validierenden XML-Katalogspeicher.
|
||||
/// </summary>
|
||||
/// <param name="path">Absoluter Zielpfad der Katalogdatei.</param>
|
||||
/// <param name="maxBytes">Maximal akzeptierte Dateigroesse.</param>
|
||||
/// <param name="maxEntries">Maximal akzeptierte Anzahl Eintraege.</param>
|
||||
public EndpointCatalogStore(string path, int maxBytes, int maxEntries)
|
||||
{
|
||||
_path = path;
|
||||
@@ -27,6 +33,11 @@ namespace BizTalkCheckmkPulse
|
||||
_maxEntries = maxEntries;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest und validiert einen maschinen- und umgebungsgebundenen Katalog.
|
||||
/// </summary>
|
||||
/// <param name="environmentName">Erwartete Monitoring-Umgebung.</param>
|
||||
/// <returns>Vollstaendig validierter Endpoint-Katalog.</returns>
|
||||
public EndpointCatalog Read(string environmentName)
|
||||
{
|
||||
var info = new FileInfo(_path);
|
||||
@@ -112,7 +123,9 @@ namespace BizTalkCheckmkPulse
|
||||
Host = ReadAttribute(node, "host"),
|
||||
Port = ReadInt(node, "port", 1, 65535),
|
||||
Enabled = ReadBool(node, "enabled"),
|
||||
AutoDiscovered = ReadBool(node, "autoDiscovered")
|
||||
AutoDiscovered = ReadBool(node, "autoDiscovered"),
|
||||
// Das optionale Attribut haelt bestehende Kataloge der Version 1 lesbar.
|
||||
BestEffort = ReadOptionalBool(node, "bestEffort", false)
|
||||
};
|
||||
ValidateEntry(entry);
|
||||
catalog.Entries.Add(entry);
|
||||
@@ -126,6 +139,10 @@ namespace BizTalkCheckmkPulse
|
||||
return catalog;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validiert und ersetzt den Endpoint-Katalog atomar.
|
||||
/// </summary>
|
||||
/// <param name="catalog">Zu persistierender geheimnisfreier Katalog.</param>
|
||||
public void Write(EndpointCatalog catalog)
|
||||
{
|
||||
if (catalog == null)
|
||||
@@ -170,7 +187,8 @@ namespace BizTalkCheckmkPulse
|
||||
new XAttribute("host", entry.Host),
|
||||
new XAttribute("port", entry.Port),
|
||||
new XAttribute("enabled", entry.Enabled),
|
||||
new XAttribute("autoDiscovered", entry.AutoDiscovered)));
|
||||
new XAttribute("autoDiscovered", entry.AutoDiscovered),
|
||||
new XAttribute("bestEffort", entry.BestEffort)));
|
||||
}
|
||||
|
||||
var document = new XDocument(new XDeclaration("1.0", "utf-8", null), root);
|
||||
@@ -284,5 +302,29 @@ namespace BizTalkCheckmkPulse
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest ein optionales boolesches Attribut mit rueckwaertskompatiblem Standardwert.
|
||||
/// </summary>
|
||||
/// <param name="element">XML-Element mit dem optionalen Attribut.</param>
|
||||
/// <param name="name">Name des Attributs.</param>
|
||||
/// <param name="defaultValue">Rueckgabewert, wenn das Attribut fehlt.</param>
|
||||
/// <returns>Gelesener Wert oder der angegebene Standardwert.</returns>
|
||||
private static bool ReadOptionalBool(XElement element, string name, bool defaultValue)
|
||||
{
|
||||
var attribute = element.Attribute(name);
|
||||
if (attribute == null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
bool value;
|
||||
if (!bool.TryParse(attribute.Value.Trim(), out value))
|
||||
{
|
||||
throw new InvalidDataException("Endpoint catalog boolean is invalid: " + name);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,21 @@ namespace BizTalkCheckmkPulse
|
||||
private readonly MonitoringOptions _options;
|
||||
private readonly FileLogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initialisiert die Endpoint-Pruefung mit Laufzeitgrenzen und Dateiprotokoll.
|
||||
/// </summary>
|
||||
/// <param name="options">Validierte Monitoring-Konfiguration.</param>
|
||||
/// <param name="logger">Gemeinsames Provider-Dateiprotokoll.</param>
|
||||
public EndpointConnectivityProbe(MonitoringOptions options, FileLogger logger)
|
||||
{
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronisiert den Katalog und ergaenzt das Gesamtergebnis um Socket-Probes.
|
||||
/// </summary>
|
||||
/// <param name="result">Bereits mit aktuellen BizTalk-Artefakten gefuelltes Ergebnis.</param>
|
||||
public void Query(ProbeResult result)
|
||||
{
|
||||
var state = result.EndpointConnectivity;
|
||||
@@ -141,7 +150,13 @@ namespace BizTalkCheckmkPulse
|
||||
foreach (var probeResult in probeResults)
|
||||
{
|
||||
state.Results.Add(probeResult);
|
||||
if (!probeResult.Available)
|
||||
if (!probeResult.Available && probeResult.Endpoint.BestEffort)
|
||||
{
|
||||
// DATABASE-/WCF-SAP-Ziele sind bewusst Diagnose-Probes: Ein
|
||||
// Fehlschlag darf weder Tageslog noch Checkmk mit Alarmen fluten.
|
||||
state.BestEffortIgnoredFailures++;
|
||||
}
|
||||
else if (!probeResult.Available)
|
||||
{
|
||||
_logger.Warning(
|
||||
"Endpoint unavailable. endpoint=" + Display(probeResult.Endpoint)
|
||||
@@ -157,6 +172,13 @@ namespace BizTalkCheckmkPulse
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fuehrt manuelle Overrides mit den aktuell automatisch erkannten Zielen zusammen.
|
||||
/// </summary>
|
||||
/// <param name="existing">Vorhandener Katalog oder <see langword="null"/>.</param>
|
||||
/// <param name="candidates">Aktuelle BizTalk-Send-/Receive-Transporte.</param>
|
||||
/// <param name="synchronizedUtc">UTC-Zeitpunkt des vollstaendigen Abgleichs.</param>
|
||||
/// <returns>Neuer, geheimnisfreier Endpoint-Katalog.</returns>
|
||||
internal static EndpointCatalog SynchronizeCatalog(
|
||||
EndpointCatalog existing,
|
||||
IEnumerable<EndpointCandidate> candidates,
|
||||
@@ -223,6 +245,10 @@ namespace BizTalkCheckmkPulse
|
||||
/// Ermittelt aus aktuellen WMI-Kandidaten und manuellen Overrides die in diesem Lauf zu pruefenden Ziele.
|
||||
/// Automatisch erkannte Katalogeintraege werden bewusst nicht als veraltete Laufzeitquelle verwendet.
|
||||
/// </summary>
|
||||
/// <param name="catalog">Gueltiger lokaler Endpoint-Katalog.</param>
|
||||
/// <param name="candidates">Aktuelle aktive und inaktive BizTalk-Transporte.</param>
|
||||
/// <param name="state">Zu aktualisierende Laufzeitzaehler und Aufloesungsdiagnosen.</param>
|
||||
/// <returns>Aktuell aktive, deduplizierte Katalogeintraege.</returns>
|
||||
internal static EndpointCatalogEntry[] ResolveActiveEndpoints(
|
||||
EndpointCatalog catalog,
|
||||
IEnumerable<EndpointCandidate> candidates,
|
||||
@@ -284,10 +310,12 @@ namespace BizTalkCheckmkPulse
|
||||
x.Enabled
|
||||
&& !string.Equals(x.ArtifactType, "Manual", StringComparison.OrdinalIgnoreCase)
|
||||
&& !activeKeys.Contains(x.Key));
|
||||
return selected
|
||||
var resolved = selected
|
||||
.GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => x.First())
|
||||
.ToArray();
|
||||
state.BestEffortActive = resolved.Count(x => x.BestEffort);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -356,6 +384,13 @@ namespace BizTalkCheckmkPulse
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Berechnet die theoretische Obergrenze fuer vollstaendig timeoutende Zielgruppen.
|
||||
/// </summary>
|
||||
/// <param name="targetCount">Anzahl eindeutiger Socket-Ziele.</param>
|
||||
/// <param name="concurrency">Maximale parallele Probes.</param>
|
||||
/// <param name="timeoutMilliseconds">Timeout je Probe in Millisekunden.</param>
|
||||
/// <returns>Theoretische Worst-Case-Dauer in Millisekunden.</returns>
|
||||
internal static long CalculateWorstCaseProbeMilliseconds(int targetCount, int concurrency, int timeoutMilliseconds)
|
||||
{
|
||||
if (targetCount <= 0)
|
||||
@@ -465,6 +500,11 @@ namespace BizTalkCheckmkPulse
|
||||
: "SocketError=" + socket.SocketErrorCode + ": " + socket.Message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatiert ein geheimnisfreies Socket-Ziel fuer Checkmk und Dateilog.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">Zu formatierender Katalogeintrag.</param>
|
||||
/// <returns>Artefakt, Host, Port und Protokoll ohne URI-Geheimnisse.</returns>
|
||||
internal static string Display(EndpointCatalogEntry endpoint)
|
||||
{
|
||||
var artifact = string.IsNullOrWhiteSpace(endpoint.ApplicationName)
|
||||
|
||||
@@ -305,7 +305,20 @@ namespace BizTalkCheckmkPulse
|
||||
public int Port { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool AutoDiscovered { get; set; }
|
||||
/// <summary>
|
||||
/// Gibt an, dass ein automatisch abgeleiteter Adaptertest nur Diagnosewert besitzt
|
||||
/// und ein Fehlschlag deshalb keinen Checkmk-Alarm ausloest.
|
||||
/// </summary>
|
||||
public bool BestEffort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt den stabilen, adressunabhaengigen Schluessel eines BizTalk-Transports.
|
||||
/// </summary>
|
||||
/// <param name="artifactType">Typ des BizTalk-Artefakts.</param>
|
||||
/// <param name="applicationName">Name der BizTalk-Anwendung.</param>
|
||||
/// <param name="artifactName">Name des Send Ports oder der Receive Location.</param>
|
||||
/// <param name="transportRole">Primaer-, Sekundaer- oder Inbound-Rolle.</param>
|
||||
/// <returns>Normalisierter Katalogschluessel.</returns>
|
||||
public static string BuildKey(string artifactType, string applicationName, string artifactName, string transportRole)
|
||||
{
|
||||
return NormalizeKeyPart(artifactType)
|
||||
@@ -371,6 +384,14 @@ namespace BizTalkCheckmkPulse
|
||||
public int SkippedInactive { get; set; }
|
||||
public int UnresolvedActive { get; set; }
|
||||
public int ExpectedNonProbeableActive { get; set; }
|
||||
/// <summary>
|
||||
/// Anzahl der automatisch abgeleiteten, nicht alarmierenden Adapterziele.
|
||||
/// </summary>
|
||||
public int BestEffortActive { get; set; }
|
||||
/// <summary>
|
||||
/// Anzahl fehlgeschlagener Best-Effort-Probes im aktuellen Lauf.
|
||||
/// </summary>
|
||||
public int BestEffortIgnoredFailures { get; set; }
|
||||
public int ManualOverridesActive { get; set; }
|
||||
public int UniqueTargets { get; set; }
|
||||
public long ProbeDurationMilliseconds { get; set; }
|
||||
|
||||
@@ -115,7 +115,9 @@ namespace BizTalkCheckmkPulse
|
||||
+ " endpoints_active="
|
||||
+ result.EndpointConnectivity.Active
|
||||
+ " endpoints_failed="
|
||||
+ result.EndpointConnectivity.Results.Count(x => !x.Available)
|
||||
+ result.EndpointConnectivity.Results.Count(x => !x.Available && !x.Endpoint.BestEffort)
|
||||
+ " endpoints_best_effort_ignored="
|
||||
+ result.EndpointConnectivity.BestEffortIgnoredFailures
|
||||
+ " endpoints_unresolved="
|
||||
+ result.EndpointConnectivity.UnresolvedActive
|
||||
+ " endpoints_expected_non_socket="
|
||||
|
||||
@@ -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.6.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.6.0")]
|
||||
[assembly: AssemblyVersion("2.2.7.0")]
|
||||
[assembly: AssemblyFileVersion("2.2.7.0")]
|
||||
|
||||
Reference in New Issue
Block a user