Scoped rules with their own context (a Windows server, a synthetic check) that produce the same findings and outcomes as the instance rules; missing data is "ikke målbar" with the collector's reason. Thresholds are settings (SQLInsight:Servers, SQLInsight:SyntheticChecks). - Sustained CPU, low memory (share and MB), paging with low memory, commit charge. - Free disk space and "fuld om ca. N dage" forecasts. - Stopped automatic services (minus those that stop by design, and a grace period after a restart) and watched services that must run. - Windows event log groups: important kinds at once, ordinary errors when they repeat, capped per server; sample messages are sensitive. - Unexpected restart (Kernel-Power 41, EventLog 6008, BugCheck), optional uptime. - Scheduled tasks that failed or did not run as planned. - Synthetic checks failing (twice in a row), "svarer ikke, selv om tjenesten kører", slow answers, TLS certificates about to expire. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
90 lines
3.4 KiB
C#
90 lines
3.4 KiB
C#
using SQLInsight.Core.Collection;
|
|
using SQLInsight.Core.Findings;
|
|
using SQLInsight.Rules.Engine;
|
|
|
|
namespace SQLInsight.Rules.Servers;
|
|
|
|
/// <summary>
|
|
/// A rule about something that is not a SQL Server instance (a Windows server, a synthetic check). It produces the
|
|
/// same findings, explanations and outcomes as the instance rules, against its own kind of context.
|
|
/// </summary>
|
|
public interface IScopedRule<in TContext>
|
|
{
|
|
/// <summary>Stable identifier, e.g. <c>SQLI-WIN-001</c>. Never reused for a different check.</summary>
|
|
string Id { get; }
|
|
|
|
string Title { get; }
|
|
|
|
RuleEvaluation Evaluate(TContext context);
|
|
}
|
|
|
|
public abstract class ScopedRule<TContext> : IScopedRule<TContext>
|
|
{
|
|
public abstract string Id { get; }
|
|
public abstract string Title { get; }
|
|
|
|
public abstract RuleEvaluation Evaluate(TContext context);
|
|
|
|
protected Finding CreateFinding(Severity severity, string title, string explanation, string recommendation, IEnumerable<Evidence>? evidence = null,
|
|
string? subject = null, params Reference[] references) => new()
|
|
{
|
|
RuleId = Id,
|
|
Severity = severity,
|
|
Title = title,
|
|
Subject = subject,
|
|
Explanation = explanation,
|
|
Recommendation = recommendation,
|
|
Evidence = evidence?.ToList() ?? [],
|
|
References = references,
|
|
};
|
|
|
|
protected static RuleEvaluation Result(IEnumerable<Finding> findings)
|
|
{
|
|
var list = findings.ToArray();
|
|
return list.Length == 0 ? RuleEvaluation.Passed() : RuleEvaluation.Found(list);
|
|
}
|
|
}
|
|
|
|
/// <summary>Runs scoped rules. Like <see cref="RuleEngine"/>: a rule that cannot run is not measurable (with the reason) or an error, never silently passed.</summary>
|
|
public static class ScopedRuleEngine
|
|
{
|
|
public static IReadOnlyList<RuleResult> Evaluate<TContext>(IEnumerable<IScopedRule<TContext>> rules, TContext context)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(rules);
|
|
return rules.Select(rule => Evaluate(rule, context)).ToList();
|
|
}
|
|
|
|
private static RuleResult Evaluate<TContext>(IScopedRule<TContext> rule, TContext context)
|
|
{
|
|
try
|
|
{
|
|
var evaluation = rule.Evaluate(context);
|
|
return new RuleResult { RuleId = rule.Id, RuleTitle = rule.Title, Outcome = evaluation.Outcome, Findings = evaluation.Findings, Reason = evaluation.Reason };
|
|
}
|
|
catch (DataUnavailableException ex)
|
|
{
|
|
return new RuleResult
|
|
{
|
|
RuleId = rule.Id,
|
|
RuleTitle = rule.Title,
|
|
Outcome = ex.NotApplicable ? RuleOutcome.NotApplicable : RuleOutcome.NotMeasurable,
|
|
Reason = ex.Message,
|
|
};
|
|
}
|
|
#pragma warning disable CA1031 // One faulty rule must not stop the others; the failure is reported, not hidden.
|
|
catch (Exception ex)
|
|
#pragma warning restore CA1031
|
|
{
|
|
return new RuleResult { RuleId = rule.Id, RuleTitle = rule.Title, Outcome = RuleOutcome.Error, Reason = $"{ex.GetType().Name}: {ex.Message}" };
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Every rule about Windows servers and synthetic checks, for rule listings.</summary>
|
|
public static class ServerRuleCatalog
|
|
{
|
|
public static IReadOnlyList<(string Id, string Title)> All =>
|
|
WindowsServerRules.Create().Select(r => (r.Id, r.Title))
|
|
.Concat(SyntheticCheckRules.Create().Select(r => (r.Id, r.Title)))
|
|
.ToList();
|
|
}
|