Mercurial > servermonitor
comparison 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 |
comparison
equal
deleted
inserted
replaced
15:23f2e0da1094 | 16:7626b099aefd |
---|---|
6 using System.Threading.Tasks; | 6 using System.Threading.Tasks; |
7 using System.Xml.Serialization; | 7 using System.Xml.Serialization; |
8 | 8 |
9 namespace ServerMonitorApp | 9 namespace ServerMonitorApp |
10 { | 10 { |
11 /*public enum CheckType | 11 /// <summary>The possible statuses of a check.</summary> |
12 { | 12 /// <remarks> |
13 Command | 13 /// The integer values of the "completed" statuses (Success, Information, |
14 }*/ | 14 /// Warning, Error) are in ascending order of severity. |
15 | 15 /// </remarks> |
16 public enum CheckStatus | 16 public enum CheckStatus |
17 { | 17 { |
18 Success, | 18 Success, |
19 Information, | 19 Information, |
20 Warning, | 20 Warning, |
21 Error, | 21 Error, |
22 Running, | 22 Running, |
23 Disabled, | 23 Disabled, |
24 } | 24 } |
25 | 25 |
26 /// <summary>Base class for checks that run against a server and return a result status.</summary> | |
26 public abstract class Check | 27 public abstract class Check |
27 { | 28 { |
28 private static Type[] _checkTypes; | 29 private static Type[] _checkTypes; |
29 | 30 |
30 public static Type[] CheckTypes | 31 public static Type[] CheckTypes |
35 .Where(t => t.IsSubclassOf(typeof(Check))) | 36 .Where(t => t.IsSubclassOf(typeof(Check))) |
36 .OrderBy(t => t.GetAttribute<DisplayWeightAttribute>()?.DisplayWeight).ToArray()); | 37 .OrderBy(t => t.GetAttribute<DisplayWeightAttribute>()?.DisplayWeight).ToArray()); |
37 } | 38 } |
38 } | 39 } |
39 | 40 |
41 /// <summary>The internal ID of the check.</summary> | |
40 public int Id { get; set; } | 42 public int Id { get; set; } |
41 | 43 |
44 /// <summary>The display name check.</summary> | |
42 public string Name { get; set; } | 45 public string Name { get; set; } |
43 | 46 |
44 /*public CheckType Type { get; set; }*/ | 47 /// <summary>The number of milliseconds the check must complete in before reporting failure.</summary> |
45 | |
46 public int Timeout { get; set; } | 48 public int Timeout { get; set; } |
47 | 49 |
50 /// <summary>Whether the check will be executed on a schedule.</summary> | |
48 public bool Enabled { get; set; } | 51 public bool Enabled { get; set; } |
49 | 52 |
53 /// <summary>The schedule when the check will be executed.</summary> | |
50 public Schedule Schedule { get; set; } | 54 public Schedule Schedule { get; set; } |
51 | 55 |
56 /// <summary>The date and time the check was last executed.</summary> | |
52 public DateTime LastRunTime { get; set; } | 57 public DateTime LastRunTime { get; set; } |
53 | 58 |
59 /// <summary>The date and time the check was last executed on its schedule.</summary> | |
54 public DateTime LastScheduledRunTime { get; set; } | 60 public DateTime LastScheduledRunTime { get; set; } |
55 | 61 |
62 /// <summary>The date and time the check is currently scheduled to execute next.</summary> | |
56 public DateTime NextRunTime { get; set; } | 63 public DateTime NextRunTime { get; set; } |
57 | 64 |
65 /// <summary>The text output of the last execution of the check.</summary> | |
58 public string LastMessage { get; set; } | 66 public string LastMessage { get; set; } |
59 | 67 |
68 /// <summary>The current status of the check.</summary> | |
60 public CheckStatus Status { get; set; } | 69 public CheckStatus Status { get; set; } |
61 | 70 |
71 /// <summary>The status of the last check execution.</summary> | |
62 public CheckStatus LastRunStatus { get; set; } | 72 public CheckStatus LastRunStatus { get; set; } |
63 | 73 |
74 /// <summary>The severity level reported when the check execution fails.</summary> | |
64 public CheckStatus FailStatus { get; set; } | 75 public CheckStatus FailStatus { get; set; } |
65 | 76 |
77 /// <summary>The number of consecutive failed executions before the check begins reporting failure.</summary> | |
66 public int MaxConsecutiveFailures { get; set; } | 78 public int MaxConsecutiveFailures { get; set; } |
67 | 79 |
80 /// <summary>The current number of consecutive times the check has failed.</summary> | |
68 [XmlIgnore] | 81 [XmlIgnore] |
69 public int ConsecutiveFailures { get; set; } | 82 public int ConsecutiveFailures { get; set; } |
70 | 83 |
84 /// <summary>The server the check belongs to.</summary> | |
71 [XmlIgnore] | 85 [XmlIgnore] |
72 public Server Server { get; set; } | 86 public Server Server { get; set; } |
73 | 87 |
88 /// <summary>Check constructor.</summary> | |
74 public Check() | 89 public Check() |
75 { | 90 { |
91 // Set the default failure severity to Error. | |
76 FailStatus = CheckStatus.Error; | 92 FailStatus = CheckStatus.Error; |
77 } | 93 } |
78 | 94 |
95 /// <summary>Displays the check name.</summary> | |
79 public override string ToString() | 96 public override string ToString() |
80 { | 97 { |
81 return Name; | 98 return Name; |
82 } | 99 } |
83 | 100 |
101 /// <summary>Validates the check.</summary> | |
102 /// <param name="saving"> | |
103 /// Whether the check is being saved. | |
104 /// Checks are validated before being saved and before being executed. This parameter allows | |
105 /// them to distinguish between these cases. | |
106 /// </param> | |
107 /// <returns>An empty string if the check is valid, or the reason the check is invalid.</returns> | |
84 public virtual string Validate(bool saving = true) | 108 public virtual string Validate(bool saving = true) |
85 { | 109 { |
86 string message = string.Empty; | 110 string message = string.Empty; |
111 // Allow blank names if the check is being executed before saving. | |
112 // This lets the user create a check and tinker with it without | |
113 // needing to type a name unless they want to save it. | |
87 if (Name.IsNullOrEmpty() && saving) | 114 if (Name.IsNullOrEmpty() && saving) |
88 message += "Name cannot be blank." + Environment.NewLine; | 115 message += "Name cannot be blank." + Environment.NewLine; |
89 return message; | 116 return message; |
90 } | 117 } |
91 | 118 |
119 /// <summary>Executes the check asynchronously.</summary> | |
120 /// <param name="token">A token for cancelling the execution.</param> | |
121 /// <param name="update">Whether the check status and last execution time should be updated when the check completes.</param> | |
122 /// <returns>The result of the check execution.</returns> | |
92 public async Task<CheckResult> ExecuteAsync(CancellationToken token = default(CancellationToken), bool update = true) | 123 public async Task<CheckResult> ExecuteAsync(CancellationToken token = default(CancellationToken), bool update = true) |
93 { | 124 { |
125 // Do nothing if the check has already been cancelled. | |
94 if (token.IsCancellationRequested) | 126 if (token.IsCancellationRequested) |
95 return null; | 127 return null; |
96 | 128 |
97 CheckResult result; | 129 CheckResult result; |
98 DateTime startTime = DateTime.Now; | 130 DateTime startTime = DateTime.Now; |
99 try | 131 try |
100 { | 132 { |
133 // Execute the check. | |
101 Task<CheckResult> checkTask = ExecuteCheckAsync(token); | 134 Task<CheckResult> checkTask = ExecuteCheckAsync(token); |
102 try | 135 try |
103 { | 136 { |
137 // Wait for the check to complete or timeout, whichever happens first. | |
104 if (await Task.WhenAny(checkTask, Task.Delay(Timeout, token)) == checkTask) | 138 if (await Task.WhenAny(checkTask, Task.Delay(Timeout, token)) == checkTask) |
105 { | 139 { |
140 // If the check completed before timing out, retrieve the result. | |
106 result = await checkTask; | 141 result = await checkTask; |
107 } | 142 } |
108 else | 143 else |
109 { | 144 { |
145 // If the check timed out before completing, report failure. | |
110 result = Fail("Timed out."); | 146 result = Fail("Timed out."); |
111 } | 147 } |
112 } | 148 } |
113 catch (TaskCanceledException) | 149 catch (TaskCanceledException) |
114 { | 150 { |
151 // If the check was cancelled, do not return a result so it will not be logged. | |
115 return null; | 152 return null; |
116 } | 153 } |
117 } | 154 } |
118 catch (Exception e) | 155 catch (Exception e) |
119 { | 156 { |
157 // If the execution threw an exception, report the exception as a failure. | |
120 result = Fail(e.GetBaseException().Message); | 158 result = Fail(e.GetBaseException().Message); |
121 } | 159 } |
122 result.StartTime = startTime; | 160 result.StartTime = startTime; |
123 result.EndTime = DateTime.Now; | 161 result.EndTime = DateTime.Now; |
124 // If a check is executed from the CheckForm, we don't want to update the status or log the event. | |
125 if (update) | 162 if (update) |
126 { | 163 { |
127 Status = result.CheckStatus; | 164 Status = result.CheckStatus; |
128 LastRunStatus = result.CheckStatus; | 165 LastRunStatus = result.CheckStatus; |
129 LastMessage = result.Message; | 166 LastMessage = result.Message; |
130 LastRunTime = result.EndTime; | 167 LastRunTime = result.EndTime; |
131 } | 168 } |
132 return result; | 169 return result; |
133 } | 170 } |
134 | 171 |
172 /// <summary>Generates a successful check result.</summary> | |
173 /// <param name="message">The execution result message.</param> | |
174 /// <returns>A successful check result.</returns> | |
135 public CheckResult Pass(string message) | 175 public CheckResult Pass(string message) |
136 { | 176 { |
137 return new CheckResult(this, CheckStatus.Success, message); | 177 return new CheckResult(this, CheckStatus.Success, message); |
138 } | 178 } |
139 | 179 |
180 /// <summary>Generates a failed check result.</summary> | |
181 /// <param name="message">The execution result message.</param> | |
182 /// <returns>A failed check result.</returns> | |
140 public CheckResult Fail(string message) | 183 public CheckResult Fail(string message) |
141 { | 184 { |
185 // The severity is controlled by the check's FailStatus setting. | |
142 return new CheckResult(this, FailStatus, message); | 186 return new CheckResult(this, FailStatus, message); |
143 } | 187 } |
144 | 188 |
189 /// <summary>Generates a failed check result from an exception.</summary> | |
190 /// <param name="e">The exception that caused the failure.</param> | |
191 /// <returns>A failed check result.</returns> | |
145 protected CheckResult Fail(Exception e) | 192 protected CheckResult Fail(Exception e) |
146 { | 193 { |
147 return new CheckResult(this, FailStatus, e.GetBaseException().Message); | 194 return Fail(e.GetBaseException().Message); |
148 } | 195 } |
149 | 196 |
197 /// <summary>Generates a check result by comparing integer values for equality.</summary> | |
198 /// <param name="expectedValue">The expected result value.</param> | |
199 /// <param name="resultValue">The actual result value generated by the check execution.</param> | |
200 /// <param name="description">Description of what the integer represents to use in the check result message. Example: "Exit code".</param> | |
201 /// <returns>A successful check result if the values are equal, or a failed check result if they are unequal.</returns> | |
150 protected CheckResult GetIntResult(int expectedValue, int resultValue, string description) | 202 protected CheckResult GetIntResult(int expectedValue, int resultValue, string description) |
151 { | 203 { |
152 if (expectedValue == resultValue) | 204 if (expectedValue == resultValue) |
153 return Pass(string.Format("{0}: {1}", description, resultValue)); | 205 return Pass(string.Format("{0}: {1}", description, resultValue)); |
154 else | 206 else |
155 return Fail(string.Format("{0}: {1} (expected: {2})", description, resultValue, expectedValue)); | 207 return Fail(string.Format("{0}: {1} (expected: {2})", description, resultValue, expectedValue)); |
156 } | 208 } |
157 | 209 |
210 /// <summary>Generates a check result by comparing string values.</summary> | |
211 /// <param name="matchType">The comparison that will be used on the strings.</param> | |
212 /// <param name="expectedPattern">The expected pattern to test the result against.</param> | |
213 /// <param name="useRegex">Whether the expected pattern should be treated as a regular expression.</param> | |
214 /// <param name="resultValue">The actual result value generated by the check execution.</param> | |
215 /// <param name="description">Description of what the string represents to use in the check result message.</param> | |
216 /// <returns>A successful check result if the string comparison succeeds, or a failed check result if it fails.</returns> | |
158 protected CheckResult GetStringResult(MatchType matchType, string expectedPattern, bool useRegex, string resultValue, string description) | 217 protected CheckResult GetStringResult(MatchType matchType, string expectedPattern, bool useRegex, string resultValue, string description) |
159 { | 218 { |
160 bool match; | 219 bool match; |
161 if (useRegex) | 220 if (useRegex) |
162 { | 221 { |
222 // If the match type is equals or not equals, modify the regex by | |
223 // adding beginning and ending anchors if not already present | |
224 // to prevent partial matches. | |
163 if (matchType.In(MatchType.Equals, MatchType.NotEquals)) | 225 if (matchType.In(MatchType.Equals, MatchType.NotEquals)) |
164 { | 226 { |
165 if (!expectedPattern.StartsWith("^")) | 227 if (!expectedPattern.StartsWith("^")) |
166 expectedPattern = "^" + expectedPattern; | 228 expectedPattern = "^" + expectedPattern; |
167 if (!expectedPattern.EndsWith("$")) | 229 if (!expectedPattern.EndsWith("$")) |
168 expectedPattern += "$"; | 230 expectedPattern += "$"; |
169 } | 231 } |
232 // Execute the regex. | |
170 Regex re = new Regex(expectedPattern, RegexOptions.Singleline); | 233 Regex re = new Regex(expectedPattern, RegexOptions.Singleline); |
171 match = re.IsMatch(resultValue); | 234 match = re.IsMatch(resultValue); |
172 } | 235 } |
173 else | 236 else |
174 { | 237 { |
238 // Simple string comparisons. | |
175 if (matchType.In(MatchType.Equals, MatchType.NotEquals)) | 239 if (matchType.In(MatchType.Equals, MatchType.NotEquals)) |
176 { | 240 { |
177 match = expectedPattern == resultValue; | 241 match = expectedPattern == resultValue; |
178 } | 242 } |
179 else if (matchType.In(MatchType.Contains, MatchType.NotContains)) | 243 else if (matchType.In(MatchType.Contains, MatchType.NotContains)) |
180 { | 244 { |
181 match = resultValue.Contains(expectedPattern); | 245 match = resultValue.Contains(expectedPattern); |
182 } | 246 } |
183 else | 247 else |
184 { | 248 { |
249 // If the match type is greater or less than, the values must be numeric. | |
185 if (decimal.TryParse(expectedPattern, out decimal expectedNumeric) && | 250 if (decimal.TryParse(expectedPattern, out decimal expectedNumeric) && |
186 decimal.TryParse(resultValue, out decimal resultNumeric)) | 251 decimal.TryParse(resultValue, out decimal resultNumeric)) |
187 { | 252 { |
253 // Compare the resulting decimals. | |
188 match = (matchType == MatchType.GreaterThan && resultNumeric > expectedNumeric) || | 254 match = (matchType == MatchType.GreaterThan && resultNumeric > expectedNumeric) || |
189 (matchType == MatchType.LessThan && resultNumeric < expectedNumeric); | 255 (matchType == MatchType.LessThan && resultNumeric < expectedNumeric); |
190 } | 256 } |
191 else | 257 else |
192 { | 258 { |
193 return Fail(string.Format("{0} is not numeric: {1}", description, resultValue)); | 259 return Fail(string.Format("{0} is not numeric: {1}", description, resultValue)); |
194 } | 260 } |
195 } | 261 } |
196 } | 262 } |
197 | 263 |
264 // We have determined whether the result value matches the expected pattern. | |
265 // Generate a check result accordingly. | |
198 if (matchType.In(MatchType.Equals, MatchType.Contains)) | 266 if (matchType.In(MatchType.Equals, MatchType.Contains)) |
199 { | 267 { |
268 // Equals, Contains: the strings are supposed to match. | |
200 if (match) | 269 if (match) |
201 return Pass(string.Format("{0} {1} the pattern: {2}", description, matchType.ToString().ToLower(), expectedPattern)); | 270 return Pass(string.Format("{0} {1} the pattern: {2}", description, matchType.ToString().ToLower(), expectedPattern)); |
202 else | 271 else |
203 return Fail(string.Format("{0} does not {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().TrimEnd('s'), expectedPattern, resultValue)); | 272 return Fail(string.Format("{0} does not {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().TrimEnd('s'), expectedPattern, resultValue)); |
204 } | 273 } |
205 else if (matchType.In(MatchType.NotEquals, MatchType.NotContains)) | 274 else if (matchType.In(MatchType.NotEquals, MatchType.NotContains)) |
206 { | 275 { |
276 // NotEquals, NotContains: the strings are not supposed to match. | |
277 // So, fail if they do match and pass if they do not. | |
207 if (match) | 278 if (match) |
208 return Fail(string.Format("{0} {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().Replace("not", ""), expectedPattern, resultValue)); | 279 return Fail(string.Format("{0} {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().Replace("not", ""), expectedPattern, resultValue)); |
209 else | 280 else |
210 return Pass(string.Format("{0} does not {1} the pattern: {2}", description, matchType.ToString().ToLower().TrimEnd('s').Replace("not", ""), expectedPattern)); | 281 return Pass(string.Format("{0} does not {1} the pattern: {2}", description, matchType.ToString().ToLower().TrimEnd('s').Replace("not", ""), expectedPattern)); |
211 } | 282 } |
212 else | 283 else |
213 { | 284 { |
285 // GreaterThan, LessThan | |
214 if (match) | 286 if (match) |
215 return Pass(string.Format("{0} ({1}) is {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); | 287 return Pass(string.Format("{0} ({1}) is {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); |
216 else | 288 else |
217 return Fail(string.Format("{0} ({1}) is not {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); | 289 return Fail(string.Format("{0} ({1}) is not {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); |
218 } | 290 } |
219 } | 291 } |
220 | 292 |
293 /// <summary>Merges multiple execution results.</summary> | |
294 /// <param name="results">The results to merge.</param> | |
295 /// <returns>A single result containing the messages of all the input results, and a failure status if any of the input results failed.</returns> | |
296 /// <remarks> | |
297 /// Some checks may want to run several tests on the result of a remote command. | |
298 /// After collecting their results, they can use this method to combine them into | |
299 /// a single result that will be reported to the user. | |
300 /// </remarks> | |
221 protected CheckResult MergeResults(params CheckResult[] results) | 301 protected CheckResult MergeResults(params CheckResult[] results) |
222 { | 302 { |
223 StringBuilder message = new StringBuilder(); | 303 StringBuilder message = new StringBuilder(); |
224 bool failed = false; | 304 bool failed = false; |
225 foreach (CheckResult result in results) | 305 foreach (CheckResult result in results) |
226 { | 306 { |
227 if (result == null) | 307 if (result == null) |
228 continue; | 308 continue; |
309 // Report failure if any of the results has failed. | |
229 if (result.Failed) | 310 if (result.Failed) |
230 failed = true; | 311 failed = true; |
312 // Merge the result messages. | |
231 message.AppendLine(result.Message); | 313 message.AppendLine(result.Message); |
232 } | 314 } |
233 return failed ? Fail(message.ToString().Trim()) : Pass(message.ToString().Trim()); | 315 return failed ? Fail(message.ToString().Trim()) : Pass(message.ToString().Trim()); |
234 } | 316 } |
235 | 317 |
318 /// <summary>Executes the check asynchronously.</summary> | |
319 /// <param name="token">A token for cancelling the execution.</param> | |
320 /// <returns>The result of the check execution.</returns> | |
236 protected abstract Task<CheckResult> ExecuteCheckAsync(CancellationToken token); | 321 protected abstract Task<CheckResult> ExecuteCheckAsync(CancellationToken token); |
237 } | 322 } |
238 } | 323 } |