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] }; } } }