using System;
using System.Collections.Generic;
using System.Linq;
namespace BizTalkCheckmkPulse
{
///
/// Validiert die vom ersten Providerlauf erzeugten Runtime-Artefakte mit denselben
/// Parsern und Grenzen, die auch der laufende Consumer verwendet.
///
internal static class RuntimeValidator
{
public static RuntimeValidationResult Validate(
MonitoringOptions options,
CheckmkLocalFormatter formatter,
DateTime notBeforeUtc,
string expectedIdentity,
DateTime utcNow)
{
if (options == null) throw new ArgumentNullException("options");
if (formatter == null) throw new ArgumentNullException("formatter");
var snapshot = new SnapshotStore(options.SnapshotPath, options.SnapshotMaxBytes)
.Read(utcNow, TimeSpan.FromSeconds(options.SnapshotMaxAgeSeconds));
if (!snapshot.IsSuccess)
{
return RuntimeValidationResult.Failed("Snapshot ist ungueltig: " + snapshot.Error);
}
var boundary = notBeforeUtc.ToUniversalTime();
if (snapshot.GeneratedUtc < boundary)
{
return RuntimeValidationResult.Failed(
"Snapshot stammt nicht aus dem gestarteten Providerlauf. generatedUtc="
+ snapshot.GeneratedUtc.ToString("o") + ", requiredUtc=" + boundary.ToString("o") + ".");
}
if (!string.Equals(snapshot.Identity, expectedIdentity, StringComparison.OrdinalIgnoreCase))
{
return RuntimeValidationResult.Failed(
"Snapshot wurde von einer unerwarteten Identitaet erzeugt. expected="
+ expectedIdentity + ", actual=" + (snapshot.Identity ?? "(leer)") + ".");
}
string serviceError;
string[] unknownServiceLines;
var expectedServices = formatter.FormatSelfTest().Select(ExtractServiceName).ToArray();
if (!ContainsExactStableContract(
snapshot.Lines,
expectedServices,
out unknownServiceLines,
out serviceError))
{
return RuntimeValidationResult.Failed(serviceError);
}
var acceptedEndpointUnknownName = options.ServiceName("Endpoint Reachability");
var blockingUnknown = unknownServiceLines
.Where(x => !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)
{
EndpointCatalog catalog;
try
{
catalog = new EndpointCatalogStore(
options.EndpointCatalogPath,
options.EndpointCatalogMaxBytes,
options.EndpointCatalogMaxEntries)
.Read(options.EnvironmentName);
}
catch (Exception ex)
{
return RuntimeValidationResult.Failed(
"Endpoint-Katalog ist ungueltig: " + ex.GetType().Name + ": " + ex.Message);
}
if (catalog.SynchronizedUtc < boundary)
{
return RuntimeValidationResult.Failed(
"Endpoint-Katalog wurde beim Installationslauf nicht aktualisiert. synchronizedUtc="
+ catalog.SynchronizedUtc.ToString("o") + ", requiredUtc=" + boundary.ToString("o") + ".");
}
if (catalog.SynchronizedUtc > utcNow.ToUniversalTime().AddMinutes(5))
{
return RuntimeValidationResult.Failed("Endpoint-Katalog-Zeitstempel liegt unplausibel in der Zukunft.");
}
catalogUtc = catalog.SynchronizedUtc;
}
return RuntimeValidationResult.Success(
snapshot.GeneratedUtc,
catalogUtc,
expectedServices.Length,
unknownServiceLines);
}
internal static bool ContainsExactStableContract(
IEnumerable lines,
IEnumerable expectedServices,
out string[] unknownServiceLines,
out string error)
{
unknownServiceLines = new string[0];
string[] actual;
try
{
actual = (lines ?? Enumerable.Empty()).Select(ExtractServiceName).ToArray();
}
catch (Exception ex)
{
error = "Snapshot-Servicevertrag kann nicht gelesen werden: " + ex.Message;
return false;
}
if (actual.Distinct(StringComparer.Ordinal).Count() != actual.Length)
{
error = "Snapshot enthaelt doppelte Checkmk-Servicenamen.";
return false;
}
var actualSet = new HashSet(actual, StringComparer.Ordinal);
var expected = (expectedServices ?? Enumerable.Empty()).ToArray();
var missing = expected
.Where(x => !actualSet.Contains(x))
.OrderBy(x => x, StringComparer.Ordinal)
.ToArray();
if (missing.Length != 0)
{
error = "Snapshot enthaelt nicht alle stabilen Checkmk-Services. Fehlend=["
+ string.Join(", ", missing) + "].";
return false;
}
unknownServiceLines = (lines ?? Enumerable.Empty())
.Where(x => x != null && x.StartsWith("3 ", StringComparison.Ordinal))
.Where(x => expected.Contains(ExtractServiceName(x), StringComparer.Ordinal))
.OrderBy(ExtractServiceName, StringComparer.Ordinal)
.ToArray();
error = string.Empty;
return true;
}
private static string ExtractServiceName(string line)
{
var firstQuote = (line ?? string.Empty).IndexOf('"');
var secondQuote = firstQuote < 0 ? -1 : line.IndexOf('"', firstQuote + 1);
if (firstQuote < 0 || secondQuote <= firstQuote + 1)
throw new InvalidOperationException("Ungueltige Local-Check-Zeile.");
return line.Substring(firstQuote + 1, secondQuote - firstQuote - 1);
}
}
internal sealed class RuntimeValidationResult
{
private RuntimeValidationResult() { }
public bool IsSuccess { get; private set; }
public string Error { get; private set; }
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,
IEnumerable acceptedUnknownLines)
{
return new RuntimeValidationResult
{
IsSuccess = true,
GeneratedUtc = generatedUtc,
CatalogSynchronizedUtc = catalogUtc,
StableServiceCount = serviceCount,
AcceptedUnknownLines = (acceptedUnknownLines ?? Enumerable.Empty()).ToArray()
};
}
public static RuntimeValidationResult Failed(string error)
{
return new RuntimeValidationResult
{
Error = error,
AcceptedUnknownLines = new string[0]
};
}
}
}