Mercurial > servermonitor
diff ServerMonitor/Objects/Checks/Check.cs @ 16:7626b099aefd
More comments.
author | Brad Greco <brad@bgreco.net> |
---|---|
date | Tue, 30 Apr 2019 20:40:58 -0400 |
parents | 052aa62cb42a |
children | 7645122aa7a9 |
line wrap: on
line diff
--- a/ServerMonitor/Objects/Checks/Check.cs Mon Apr 22 21:11:27 2019 -0400 +++ b/ServerMonitor/Objects/Checks/Check.cs Tue Apr 30 20:40:58 2019 -0400 @@ -8,11 +8,11 @@ namespace ServerMonitorApp { - /*public enum CheckType - { - Command - }*/ - + /// <summary>The possible statuses of a check.</summary> + /// <remarks> + /// The integer values of the "completed" statuses (Success, Information, + /// Warning, Error) are in ascending order of severity. + /// </remarks> public enum CheckStatus { Success, @@ -23,6 +23,7 @@ Disabled, } + /// <summary>Base class for checks that run against a server and return a result status.</summary> public abstract class Check { private static Type[] _checkTypes; @@ -37,60 +38,91 @@ } } + /// <summary>The internal ID of the check.</summary> public int Id { get; set; } + /// <summary>The display name check.</summary> public string Name { get; set; } - /*public CheckType Type { get; set; }*/ - + /// <summary>The number of milliseconds the check must complete in before reporting failure.</summary> public int Timeout { get; set; } + /// <summary>Whether the check will be executed on a schedule.</summary> public bool Enabled { get; set; } + /// <summary>The schedule when the check will be executed.</summary> public Schedule Schedule { get; set; } + /// <summary>The date and time the check was last executed.</summary> public DateTime LastRunTime { get; set; } + /// <summary>The date and time the check was last executed on its schedule.</summary> public DateTime LastScheduledRunTime { get; set; } + /// <summary>The date and time the check is currently scheduled to execute next.</summary> public DateTime NextRunTime { get; set; } + /// <summary>The text output of the last execution of the check.</summary> public string LastMessage { get; set; } + /// <summary>The current status of the check.</summary> public CheckStatus Status { get; set; } + /// <summary>The status of the last check execution.</summary> public CheckStatus LastRunStatus { get; set; } + /// <summary>The severity level reported when the check execution fails.</summary> public CheckStatus FailStatus { get; set; } + /// <summary>The number of consecutive failed executions before the check begins reporting failure.</summary> public int MaxConsecutiveFailures { get; set; } + /// <summary>The current number of consecutive times the check has failed.</summary> [XmlIgnore] public int ConsecutiveFailures { get; set; } + /// <summary>The server the check belongs to.</summary> [XmlIgnore] public Server Server { get; set; } + /// <summary>Check constructor.</summary> public Check() { + // Set the default failure severity to Error. FailStatus = CheckStatus.Error; } + /// <summary>Displays the check name.</summary> public override string ToString() { return Name; } + /// <summary>Validates the check.</summary> + /// <param name="saving"> + /// Whether the check is being saved. + /// Checks are validated before being saved and before being executed. This parameter allows + /// them to distinguish between these cases. + /// </param> + /// <returns>An empty string if the check is valid, or the reason the check is invalid.</returns> public virtual string Validate(bool saving = true) { string message = string.Empty; + // Allow blank names if the check is being executed before saving. + // This lets the user create a check and tinker with it without + // needing to type a name unless they want to save it. if (Name.IsNullOrEmpty() && saving) message += "Name cannot be blank." + Environment.NewLine; return message; } + /// <summary>Executes the check asynchronously.</summary> + /// <param name="token">A token for cancelling the execution.</param> + /// <param name="update">Whether the check status and last execution time should be updated when the check completes.</param> + /// <returns>The result of the check execution.</returns> public async Task<CheckResult> ExecuteAsync(CancellationToken token = default(CancellationToken), bool update = true) { + // Do nothing if the check has already been cancelled. if (token.IsCancellationRequested) return null; @@ -98,30 +130,35 @@ DateTime startTime = DateTime.Now; try { + // Execute the check. Task<CheckResult> checkTask = ExecuteCheckAsync(token); try { + // Wait for the check to complete or timeout, whichever happens first. if (await Task.WhenAny(checkTask, Task.Delay(Timeout, token)) == checkTask) { + // If the check completed before timing out, retrieve the result. result = await checkTask; } else { + // If the check timed out before completing, report failure. result = Fail("Timed out."); } } catch (TaskCanceledException) { + // If the check was cancelled, do not return a result so it will not be logged. return null; } } catch (Exception e) { + // If the execution threw an exception, report the exception as a failure. result = Fail(e.GetBaseException().Message); } result.StartTime = startTime; result.EndTime = DateTime.Now; - // If a check is executed from the CheckForm, we don't want to update the status or log the event. if (update) { Status = result.CheckStatus; @@ -132,21 +169,36 @@ return result; } + /// <summary>Generates a successful check result.</summary> + /// <param name="message">The execution result message.</param> + /// <returns>A successful check result.</returns> public CheckResult Pass(string message) { return new CheckResult(this, CheckStatus.Success, message); } + /// <summary>Generates a failed check result.</summary> + /// <param name="message">The execution result message.</param> + /// <returns>A failed check result.</returns> public CheckResult Fail(string message) { + // The severity is controlled by the check's FailStatus setting. return new CheckResult(this, FailStatus, message); } + /// <summary>Generates a failed check result from an exception.</summary> + /// <param name="e">The exception that caused the failure.</param> + /// <returns>A failed check result.</returns> protected CheckResult Fail(Exception e) { - return new CheckResult(this, FailStatus, e.GetBaseException().Message); + return Fail(e.GetBaseException().Message); } + /// <summary>Generates a check result by comparing integer values for equality.</summary> + /// <param name="expectedValue">The expected result value.</param> + /// <param name="resultValue">The actual result value generated by the check execution.</param> + /// <param name="description">Description of what the integer represents to use in the check result message. Example: "Exit code".</param> + /// <returns>A successful check result if the values are equal, or a failed check result if they are unequal.</returns> protected CheckResult GetIntResult(int expectedValue, int resultValue, string description) { if (expectedValue == resultValue) @@ -155,11 +207,21 @@ return Fail(string.Format("{0}: {1} (expected: {2})", description, resultValue, expectedValue)); } + /// <summary>Generates a check result by comparing string values.</summary> + /// <param name="matchType">The comparison that will be used on the strings.</param> + /// <param name="expectedPattern">The expected pattern to test the result against.</param> + /// <param name="useRegex">Whether the expected pattern should be treated as a regular expression.</param> + /// <param name="resultValue">The actual result value generated by the check execution.</param> + /// <param name="description">Description of what the string represents to use in the check result message.</param> + /// <returns>A successful check result if the string comparison succeeds, or a failed check result if it fails.</returns> protected CheckResult GetStringResult(MatchType matchType, string expectedPattern, bool useRegex, string resultValue, string description) { bool match; if (useRegex) { + // If the match type is equals or not equals, modify the regex by + // adding beginning and ending anchors if not already present + // to prevent partial matches. if (matchType.In(MatchType.Equals, MatchType.NotEquals)) { if (!expectedPattern.StartsWith("^")) @@ -167,11 +229,13 @@ if (!expectedPattern.EndsWith("$")) expectedPattern += "$"; } + // Execute the regex. Regex re = new Regex(expectedPattern, RegexOptions.Singleline); match = re.IsMatch(resultValue); } else { + // Simple string comparisons. if (matchType.In(MatchType.Equals, MatchType.NotEquals)) { match = expectedPattern == resultValue; @@ -182,9 +246,11 @@ } else { + // If the match type is greater or less than, the values must be numeric. if (decimal.TryParse(expectedPattern, out decimal expectedNumeric) && decimal.TryParse(resultValue, out decimal resultNumeric)) { + // Compare the resulting decimals. match = (matchType == MatchType.GreaterThan && resultNumeric > expectedNumeric) || (matchType == MatchType.LessThan && resultNumeric < expectedNumeric); } @@ -195,8 +261,11 @@ } } + // We have determined whether the result value matches the expected pattern. + // Generate a check result accordingly. if (matchType.In(MatchType.Equals, MatchType.Contains)) { + // Equals, Contains: the strings are supposed to match. if (match) return Pass(string.Format("{0} {1} the pattern: {2}", description, matchType.ToString().ToLower(), expectedPattern)); else @@ -204,6 +273,8 @@ } else if (matchType.In(MatchType.NotEquals, MatchType.NotContains)) { + // NotEquals, NotContains: the strings are not supposed to match. + // So, fail if they do match and pass if they do not. if (match) return Fail(string.Format("{0} {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().Replace("not", ""), expectedPattern, resultValue)); else @@ -211,6 +282,7 @@ } else { + // GreaterThan, LessThan if (match) return Pass(string.Format("{0} ({1}) is {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); else @@ -218,6 +290,14 @@ } } + /// <summary>Merges multiple execution results.</summary> + /// <param name="results">The results to merge.</param> + /// <returns>A single result containing the messages of all the input results, and a failure status if any of the input results failed.</returns> + /// <remarks> + /// Some checks may want to run several tests on the result of a remote command. + /// After collecting their results, they can use this method to combine them into + /// a single result that will be reported to the user. + /// </remarks> protected CheckResult MergeResults(params CheckResult[] results) { StringBuilder message = new StringBuilder(); @@ -226,13 +306,18 @@ { if (result == null) continue; + // Report failure if any of the results has failed. if (result.Failed) failed = true; + // Merge the result messages. message.AppendLine(result.Message); } return failed ? Fail(message.ToString().Trim()) : Pass(message.ToString().Trim()); } + /// <summary>Executes the check asynchronously.</summary> + /// <param name="token">A token for cancelling the execution.</param> + /// <returns>The result of the check execution.</returns> protected abstract Task<CheckResult> ExecuteCheckAsync(CancellationToken token); } } \ No newline at end of file