Add BizTalk endpoint reachability monitoring

This commit is contained in:
2026-08-04 09:48:00 +02:00
parent 826d87fef7
commit 91e0db5619
20 changed files with 1714 additions and 38 deletions
+172 -7
View File
@@ -28,6 +28,10 @@ namespace BizTalkCheckmkPulse.Tests
Run("UnknownReceiveLocationStateIsNotGreen", UnknownReceiveLocationStateIsNotGreen);
Run("SendPortAllowlistSeparatesExpectedState", SendPortAllowlistSeparatesExpectedState);
Run("ArtifactSummaryIsBounded", ArtifactSummaryIsBounded);
Run("EndpointAddressesResolveWithoutSecrets", EndpointAddressesResolveWithoutSecrets);
Run("EndpointCatalogPreservesManualOverrides", EndpointCatalogPreservesManualOverrides);
Run("EndpointCatalogRoundTrip", EndpointCatalogRoundTrip);
Run("EndpointOutputListsOnlyUnavailableTargets", EndpointOutputListsOnlyUnavailableTargets);
Run("SnapshotRoundTripPreservesLines", SnapshotRoundTripPreservesLines);
Run("SnapshotRejectsTampering", SnapshotRejectsTampering);
Run("SnapshotRejectsStaleData", SnapshotRejectsStaleData);
@@ -52,19 +56,20 @@ namespace BizTalkCheckmkPulse.Tests
private static void SelfTestEmitsAllStableServices()
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).FormatSelfTest().ToArray();
AssertEqual(8, lines.Length, "self-test line count");
AssertEqual(8, lines.Distinct(StringComparer.Ordinal).Count(), "unique self-test lines");
AssertEqual(9, lines.Length, "self-test line count");
AssertEqual(9, lines.Distinct(StringComparer.Ordinal).Count(), "unique self-test lines");
Assert(lines.All(x => x.StartsWith("0 \"BizTalk ", StringComparison.Ordinal)), "every self-test line must be OK");
Assert(lines.Any(x => x.Contains("\"BizTalk Event Log\"")), "Event Log service missing");
Assert(lines.Any(x => x.Contains("\"BizTalk Receive Locations\"")), "Receive Locations service missing");
Assert(lines.Any(x => x.Contains("\"BizTalk Send Ports\"")), "Send Ports service missing");
Assert(lines.Any(x => x.Contains("\"BizTalk Endpoint Reachability\"")), "Endpoint Reachability service missing");
Assert(lines.Any(x => x.Contains("\"BizTalk Orchestrations\"")), "Orchestrations service missing");
}
private static void UnavailableSourcesAreUnknown()
{
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).Format(new ProbeResult()).Take(8).ToArray();
AssertEqual(8, lines.Length, "stable service count");
var lines = new CheckmkLocalFormatter(new MonitoringOptions()).Format(new ProbeResult()).Take(9).ToArray();
AssertEqual(9, lines.Length, "stable service count");
Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "unavailable sources must be UNKNOWN");
}
@@ -73,7 +78,7 @@ namespace BizTalkCheckmkPulse.Tests
var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true };
var result = CreateSuspensionResult("(unknown)");
var lines = new CheckmkLocalFormatter(options).Format(result).ToArray();
AssertEqual(8, lines.Length, "unknown application must not create a dynamic service");
AssertEqual(9, lines.Length, "unknown application must not create a dynamic service");
}
private static void KnownApplicationCreatesService()
@@ -81,7 +86,7 @@ namespace BizTalkCheckmkPulse.Tests
var options = new MonitoringOptions { EmitPerApplicationSuspensionServices = true };
var result = CreateSuspensionResult("Orders");
var lines = new CheckmkLocalFormatter(options).Format(result).ToArray();
AssertEqual(9, lines.Length, "known application should create one dynamic service");
AssertEqual(10, lines.Length, "known application should create one dynamic service");
Assert(lines.Any(x => x.Contains("\"BizTalk Suspended Orders\"")), "known application service missing");
}
@@ -328,6 +333,166 @@ namespace BizTalkCheckmkPulse.Tests
});
}
private static void EndpointAddressesResolveWithoutSecrets()
{
EndpointCatalogEntry endpoint;
string reason;
var candidate = new EndpointCandidate
{
ArtifactType = "SendPort",
ApplicationName = "Orders",
ArtifactName = "SP Orders",
TransportRole = "Primary",
AdapterName = "WCF-BasicHttp",
Address = "https://api-user:top-secret@example.test/orders?q=secret",
Active = true
};
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "HTTPS endpoint should resolve: " + reason);
AssertEqual("example.test", endpoint.Host, "HTTPS host");
AssertEqual(443, endpoint.Port, "HTTPS port");
Assert(endpoint.Key.IndexOf("secret", StringComparison.OrdinalIgnoreCase) < 0, "catalog key must not contain URI secrets");
Assert(endpoint.Host.IndexOf("secret", StringComparison.OrdinalIgnoreCase) < 0, "catalog host must not contain URI secrets");
candidate.Address = @"\\fileserver\drop\%MessageID%.xml";
candidate.AdapterName = "FILE";
Assert(EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "UNC endpoint should resolve: " + reason);
AssertEqual("fileserver", endpoint.Host, "UNC host");
AssertEqual(445, endpoint.Port, "UNC SMB port");
candidate.Address = "recipient@example.test";
candidate.AdapterName = "SMTP";
Assert(!EndpointAddressParser.TryCreate(candidate, out endpoint, out reason), "SMTP recipient must not become a host probe");
Assert(!EndpointAddressParser.IsPotentialExternalEndpoint(candidate), "SMTP recipient must not cause UNKNOWN");
candidate.Address = "/Orders/Receive.svc";
candidate.AdapterName = "WCF-CustomIsolated";
Assert(!EndpointAddressParser.IsPotentialExternalEndpoint(candidate), "relative local receive address must not cause UNKNOWN");
}
private static void EndpointCatalogPreservesManualOverrides()
{
var candidate = new EndpointCandidate
{
ArtifactType = "SendPort",
ApplicationName = "Orders",
ArtifactName = "SP Orders",
TransportRole = "Primary",
AdapterName = "SFTP",
Address = "sftp://discovered.example.test/out",
Active = true
};
var inactive = new EndpointCandidate
{
ArtifactType = "ReceiveLocation",
ApplicationName = "Orders",
ArtifactName = "RL Disabled",
TransportRole = "Inbound",
AdapterName = "HTTPS",
Address = "https://disabled.example.test/in",
Active = false
};
var existing = new EndpointCatalog { EnvironmentName = "ACC" };
existing.Entries.Add(new EndpointCatalogEntry
{
Key = candidate.Key,
ArtifactType = candidate.ArtifactType,
ApplicationName = candidate.ApplicationName,
ArtifactName = candidate.ArtifactName,
TransportRole = candidate.TransportRole,
AdapterName = candidate.AdapterName,
Protocol = "TCP",
Host = "manual.example.test",
Port = 2222,
Enabled = true,
AutoDiscovered = false
});
var synchronized = EndpointConnectivityProbe.SynchronizeCatalog(existing, new[] { candidate, inactive }, DateTime.UtcNow);
AssertEqual(1, synchronized.Entries.Count, "manual override count");
AssertEqual("manual.example.test", synchronized.Entries[0].Host, "manual override host");
AssertEqual(2222, synchronized.Entries[0].Port, "manual override port");
Assert(!synchronized.Entries[0].AutoDiscovered, "manual override marker");
Assert(synchronized.Entries.All(x => x.ArtifactName != "RL Disabled"), "inactive receive location must not be added");
}
private static void EndpointOutputListsOnlyUnavailableTargets()
{
var result = new ProbeResult();
result.EndpointConnectivity.RuntimeStateAvailable = true;
result.EndpointConnectivity.CatalogAvailable = true;
result.EndpointConnectivity.RefreshSucceeded = true;
result.EndpointConnectivity.Configured = 2;
result.EndpointConnectivity.Active = 2;
result.EndpointConnectivity.Results.Add(new EndpointProbeResult
{
Endpoint = TestEndpoint("Reachable", "up.example.test"),
Available = true
});
result.EndpointConnectivity.Results.Add(new EndpointProbeResult
{
Endpoint = TestEndpoint("Unavailable", "down.example.test"),
Available = false,
Failure = "TCP timeout"
});
var line = FindServiceLine(result, "Endpoint Reachability");
Assert(line.StartsWith("2 \"BizTalk Endpoint Reachability\"", StringComparison.Ordinal), "failed endpoint must be CRIT");
Assert(line.Contains("Unavailable"), "unavailable endpoint name missing");
Assert(line.Contains("down.example.test:443/TCP"), "unavailable host missing");
Assert(line.IndexOf("Reachable", StringComparison.Ordinal) < 0, "reachable endpoint must not clutter output");
Assert(line.IndexOf("up.example.test", StringComparison.Ordinal) < 0, "reachable host must not clutter output");
}
private static void EndpointCatalogRoundTrip()
{
var directory = Path.Combine(Path.GetTempPath(), "BizTalkCheckmkPulse.CatalogTests." + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
var path = Path.Combine(directory, "endpoints.xml");
try
{
var catalog = new EndpointCatalog
{
EnvironmentName = "ACC",
SynchronizedUtc = DateTime.UtcNow,
ActiveCandidates = 1,
UnsupportedCandidates = 0
};
catalog.Entries.Add(TestEndpoint("Orders", "api.example.test"));
var store = new EndpointCatalogStore(path, 1048576, 500);
store.Write(catalog);
var loaded = store.Read("ACC");
AssertEqual(1, loaded.Entries.Count, "catalog entry count");
AssertEqual("api.example.test", loaded.Entries[0].Host, "catalog host");
AssertEqual(443, loaded.Entries[0].Port, "catalog port");
Assert(File.ReadAllText(path).IndexOf("top-secret", StringComparison.OrdinalIgnoreCase) < 0, "catalog must not contain URI secrets");
}
finally
{
if (Directory.Exists(directory))
{
Directory.Delete(directory, true);
}
}
}
private static EndpointCatalogEntry TestEndpoint(string name, string host)
{
return new EndpointCatalogEntry
{
Key = name,
ArtifactType = "SendPort",
ApplicationName = "Orders",
ArtifactName = name,
TransportRole = "Primary",
Protocol = "TCP",
Host = host,
Port = 443,
Enabled = true,
AutoDiscovered = true
};
}
private static void SnapshotRejectsTampering()
{
WithTemporarySnapshot((path, store) =>
@@ -392,7 +557,7 @@ namespace BizTalkCheckmkPulse.Tests
var lines = new CheckmkLocalFormatter(new MonitoringOptions())
.FormatSnapshotFailure("Snapshot file does not exist.")
.ToArray();
AssertEqual(8, lines.Length, "snapshot failure stable service count");
AssertEqual(9, lines.Length, "snapshot failure stable service count");
Assert(lines.All(x => x.StartsWith("3 \"BizTalk ", StringComparison.Ordinal)), "snapshot failure must be UNKNOWN");
Assert(lines.All(x => x.Contains("Scheduled Task")), "snapshot failure must contain provider action");
}