view ServerMonitor/Objects/Checks/Check.cs @ 15:23f2e0da1094

- Fix the last execution status being lost after a check is edited. - Add comments.
author Brad Greco <brad@bgreco.net>
date Mon, 22 Apr 2019 21:11:27 -0400
parents 052aa62cb42a
children 7626b099aefd
line wrap: on
line source

using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace ServerMonitorApp
{
    /*public enum CheckType
    {
        Command
    }*/

    public enum CheckStatus
    {
        Success,
        Information,
        Warning,
        Error,
        Running,
        Disabled,
    }

    public abstract class Check
    {
        private static Type[] _checkTypes;

        public static Type[] CheckTypes
        {
            get
            {
                return _checkTypes ?? (_checkTypes = typeof(Check).Assembly.GetTypes()
                    .Where(t => t.IsSubclassOf(typeof(Check)))
                    .OrderBy(t => t.GetAttribute<DisplayWeightAttribute>()?.DisplayWeight).ToArray());
            }
        }

        public int Id { get; set; }

        public string Name { get; set; }

        /*public CheckType Type { get; set; }*/

        public int Timeout { get; set; }

        public bool Enabled { get; set; }

        public Schedule Schedule { get; set; }

        public DateTime LastRunTime { get; set; }

        public DateTime LastScheduledRunTime { get; set; }

        public DateTime NextRunTime { get; set; }

        public string LastMessage { get; set; }

        public CheckStatus Status { get; set; }

        public CheckStatus LastRunStatus { get; set; }

        public CheckStatus FailStatus { get; set; }

        public int MaxConsecutiveFailures { get; set; }

        [XmlIgnore]
        public int ConsecutiveFailures { get; set; }

        [XmlIgnore]
        public Server Server { get; set; }

        public Check()
        {
            FailStatus = CheckStatus.Error;
        }

        public override string ToString()
        {
            return Name;
        }

        public virtual string Validate(bool saving = true)
        {
            string message = string.Empty;
            if (Name.IsNullOrEmpty() && saving)
                message += "Name cannot be blank." + Environment.NewLine;
            return message;
        }

        public async Task<CheckResult> ExecuteAsync(CancellationToken token = default(CancellationToken), bool update = true)
        {
            if (token.IsCancellationRequested)
                return null;

            CheckResult result;
            DateTime startTime = DateTime.Now;
            try
            {
                Task<CheckResult> checkTask = ExecuteCheckAsync(token);
                try
                {
                    if (await Task.WhenAny(checkTask, Task.Delay(Timeout, token)) == checkTask)
                    {
                        result = await checkTask;
                    }
                    else
                    {
                        result = Fail("Timed out.");
                    }
                }
                catch (TaskCanceledException)
                {
                    return null;
                }
            }
            catch (Exception e)
            {
                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;
                LastRunStatus = result.CheckStatus;
                LastMessage = result.Message;
                LastRunTime = result.EndTime;
            }
            return result;
        }

        public CheckResult Pass(string message)
        {
            return new CheckResult(this, CheckStatus.Success, message);
        }

        public CheckResult Fail(string message)
        {
            return new CheckResult(this, FailStatus, message);
        }

        protected CheckResult Fail(Exception e)
        {
            return new CheckResult(this, FailStatus, e.GetBaseException().Message);
        }

        protected CheckResult GetIntResult(int expectedValue, int resultValue, string description)
        {
            if (expectedValue == resultValue)
                return Pass(string.Format("{0}: {1}", description, resultValue));
            else
                return Fail(string.Format("{0}: {1} (expected: {2})", description, resultValue, expectedValue));
        }

        protected CheckResult GetStringResult(MatchType matchType, string expectedPattern, bool useRegex, string resultValue, string description)
        {
            bool match;
            if (useRegex)
            {
                if (matchType.In(MatchType.Equals, MatchType.NotEquals))
                {
                    if (!expectedPattern.StartsWith("^"))
                        expectedPattern = "^" + expectedPattern;
                    if (!expectedPattern.EndsWith("$"))
                        expectedPattern += "$";
                }
                Regex re = new Regex(expectedPattern, RegexOptions.Singleline);
                match = re.IsMatch(resultValue);
            }
            else
            {
                if (matchType.In(MatchType.Equals, MatchType.NotEquals))
                {
                    match = expectedPattern == resultValue;
                }
                else if (matchType.In(MatchType.Contains, MatchType.NotContains))
                {
                    match = resultValue.Contains(expectedPattern);
                }
                else
                {
                    if (decimal.TryParse(expectedPattern, out decimal expectedNumeric) &&
                        decimal.TryParse(resultValue, out decimal resultNumeric))
                    {
                        match = (matchType == MatchType.GreaterThan && resultNumeric > expectedNumeric) ||
                                (matchType == MatchType.LessThan    && resultNumeric < expectedNumeric);
                    }
                    else
                    {
                        return Fail(string.Format("{0} is not numeric: {1}", description, resultValue));
                    }
                }
            }

            if (matchType.In(MatchType.Equals, MatchType.Contains))
            {
                if (match)
                    return Pass(string.Format("{0} {1} the pattern: {2}", description, matchType.ToString().ToLower(), expectedPattern));
                else
                    return Fail(string.Format("{0} does not {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().TrimEnd('s'), expectedPattern, resultValue));
            }
            else if (matchType.In(MatchType.NotEquals, MatchType.NotContains))
            {
                if (match)
                    return Fail(string.Format("{0} {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().Replace("not", ""), expectedPattern, resultValue));
                else
                    return Pass(string.Format("{0} does not {1} the pattern: {2}", description, matchType.ToString().ToLower().TrimEnd('s').Replace("not", ""), expectedPattern));
            }
            else
            {
                if (match)
                    return Pass(string.Format("{0} ({1}) is {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern));
                else
                    return Fail(string.Format("{0} ({1}) is not {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern));
            }
        }

        protected CheckResult MergeResults(params CheckResult[] results)
        {
            StringBuilder message = new StringBuilder();
            bool failed = false;
            foreach (CheckResult result in results)
            {
                if (result == null)
                    continue;
                if (result.Failed)
                    failed = true;
                message.AppendLine(result.Message);
            }
            return failed ? Fail(message.ToString().Trim()) : Pass(message.ToString().Trim());
        }

        protected abstract Task<CheckResult> ExecuteCheckAsync(CancellationToken token);
    }
}