view ServerMonitor/Objects/Checks/DiskSpaceCheck.cs @ 23:3866c19535fd

Fix NullReferenceException when checks are executed on a brand new server.
author Brad Greco <brad@bgreco.net>
date Thu, 30 May 2019 21:40:27 -0400
parents 68d7834dc28e
children
line wrap: on
line source

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace ServerMonitorApp
{
    /// <summary>Checks the available disk space on a remote server.</summary>
    [DisplayName("Disk space check"), Description("Check the remaining free disk space"), DisplayWeight(11)]
    public class DiskSpaceCheck : SshCheck
    {
        /// <summary>The command to execute. Must return the POSIX output format of df(1).</summary>
        public override string Command => string.Format(DiskSpaceCommand, Device);

        /// <summary>The command to execute, with a placeholder of {0} for the device to check.</summary>
        protected string DiskSpaceCommand => "df -P -k {0}";

        /// <summary>The device or file on the device to check.</summary>
        public string Device { get; set; }

        /// <summary>The minimum free space allowed for the check to pass.</summary>
        public double MinFreeSpace { get; set; }

        /// <summary>The storage units or percentage for MinFreeSpace.</summary>
        public FreeSpaceUnits FreeSpaceUnits { get; set; }

        public DiskSpaceCheck()
        {
            // Set general SSH check settings for disk space checks.
            CheckExitCode = true;
            ExitCode = 0;
        }

        /// <summary>Processes the output of the disk space check command.</summary>
        protected override List<CheckResult> ProcessCommandResult(string output, int exitCode)
        {
            // Check for general SSH failures.
            List<CheckResult> results = base.ProcessCommandResult(output, exitCode);
            // If there was an error running the command, fail immediately.
            if (results.Any(r => r.Failed))
                return results;

            /* Parse the command results, expected in the POSIX output format of df(1):
               Filesystem 1024-blocks Used Available Capacity Mounted on
               <file system name> <total space> <space used> <space free> <percentage used> <file system root>
            */
            // Split the output into lines and remove the header row.
            List<string> lines = output.Split('\n').ToList();
            lines.RemoveAt(0);
            // Make sure there is only a single line of output remaining.
            if (lines.Count > 1)
            {
                results.Add(Fail("df output was more than one line: " + string.Join("\n", lines)));
            }
            else
            {
                // Split the string into tokens on whitespace.
                string[] tokens = lines[0].Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
                if (FreeSpaceUnits == FreeSpaceUnits.percent)
                {
                    // Test on percentage: calculate the capacity free percent from the capacity used percent reported.
                    if (int.TryParse(tokens[4].Replace("%", ""), out int percent))
                    {
                        percent = 100 - percent;
                        string message = string.Format("Free disk space is {0}%", percent);
                        if (percent < MinFreeSpace)
                            results.Add(Fail(message));
                        else
                            results.Add(Pass(message));
                    }
                    else
                    {
                        results.Add(Fail("Unable to parse df output as integer: " + tokens[4].Replace("%", "")));
                    }
                }
                else
                {
                    // Test on bytes: calculate the remaining available space from the reported available space.
                    if (int.TryParse(tokens[3], out int freeSpace))
                    {
                        // Available space is returned in KB. Convert to MB (our default unit).
                        freeSpace /= 1024;
                        // If the unit is GB, convert MB to GB.
                        if (FreeSpaceUnits == FreeSpaceUnits.GB)
                            freeSpace /= 1024;
                        string message = string.Format("Free disk space is {0} {1}", freeSpace, FreeSpaceUnits);
                        if (freeSpace < MinFreeSpace)
                            results.Add(Fail(message));
                        else
                            results.Add(Pass(message));
                    }
                    else
                    {
                        results.Add(Fail("Unable to parse df output as integer: " + tokens[3]));
                    }
                }
            }
            return results;
        }

        /// <summary>Validates disk space check options.</summary>
        public override string Validate(bool saving = true)
        {
            string message = base.Validate();
            if (Device.IsNullOrEmpty())
                message += "Device is required." + Environment.NewLine;
            if (MinFreeSpace <= 0)
                message += "Free space must be greater than 0." + Environment.NewLine;
            else if (FreeSpaceUnits == FreeSpaceUnits.percent && MinFreeSpace > 100)
                message += "Free space percent must be between 0 and 100." + Environment.NewLine;
            return message;
        }
    }

    /// <summary>The units to use when testing available disk space.</summary>
    public enum FreeSpaceUnits
    {
        /// <summary>Tests available disk space in megabytes.</summary>
        MB = 0,
        /// <summary>Tests available disk space in gigabytes.</summary>
        GB = 1,
        /// <summary>Tests available disk space as a percentage of the total space.</summary>
        percent = 2
    }
}