# -*- coding: utf-8 -*-
"""
Module for returning various status data about a minion.
These data can be useful for compiling into stats later.
"""

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals

import collections
import copy
import datetime
import fnmatch
import logging
import os
import re
import time

# Import salt libs
import salt.config
import salt.minion
import salt.utils.event
import salt.utils.files
import salt.utils.network
import salt.utils.path
import salt.utils.platform
import salt.utils.stringutils
from salt.exceptions import CommandExecutionError

# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import range, zip

log = logging.getLogger(__file__)

__virtualname__ = "status"
__opts__ = {}

# Don't shadow built-in's.
__func_alias__ = {"time_": "time"}


log = logging.getLogger(__name__)


def __virtual__():
    """
    Not all functions supported by Windows
    """
    if salt.utils.platform.is_windows():
        return False, "Windows platform is not supported by this module"

    return __virtualname__


def _number(text):
    """
    Convert a string to a number.
    Returns an integer if the string represents an integer, a floating
    point number if the string is a real number, or the string unchanged
    otherwise.
    """
    if text.isdigit():
        return int(text)
    try:
        return float(text)
    except ValueError:
        return text


def _get_boot_time_aix():
    """
    Return the number of seconds since boot time on AIX

    t=$(LC_ALL=POSIX ps -o etime= -p 1)
    d=0 h=0
    case $t in *-*) d=${t%%-*}; t=${t#*-};; esac
    case $t in *:*:*) h=${t%%:*}; t=${t#*:};; esac
    s=$((d*86400 + h*3600 + ${t%%:*}*60 + ${t#*:}))

    t is 7-20:46:46
    """
    boot_secs = 0
    res = __salt__["cmd.run_all"]("ps -o etime= -p 1")
    if res["retcode"] > 0:
        raise CommandExecutionError("Unable to find boot_time for pid 1.")
    bt_time = res["stdout"]
    days = bt_time.split("-")
    hms = days[1].split(":")
    boot_secs = (
        _number(days[0]) * 86400
        + _number(hms[0]) * 3600
        + _number(hms[1]) * 60
        + _number(hms[2])
    )
    return boot_secs


def _aix_loadavg():
    """
    Return the load average on AIX
    """
    #  03:42PM   up 9 days,  20:41,  2 users,  load average: 0.28, 0.47, 0.69
    uptime = __salt__["cmd.run"]("uptime")
    ldavg = uptime.split("load average")
    load_avg = ldavg[1].split()
    return {
        "1-min": load_avg[1].strip(","),
        "5-min": load_avg[2].strip(","),
        "15-min": load_avg[3],
    }


def _aix_nproc():
    """
    Return the maximun number of PROCESSES allowed per user on AIX
    """
    nprocs = __salt__["cmd.run"](
        "lsattr -E -l sys0 | grep maxuproc", python_shell=True
    ).split()
    return _number(nprocs[1])


def procs():
    """
    Return the process data

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.procs
    """
    # Get the user, pid and cmd
    ret = {}
    uind = 0
    pind = 0
    cind = 0
    plines = __salt__["cmd.run"](__grains__["ps"], python_shell=True).splitlines()
    guide = plines.pop(0).split()
    if "USER" in guide:
        uind = guide.index("USER")
    elif "UID" in guide:
        uind = guide.index("UID")
    if "PID" in guide:
        pind = guide.index("PID")
    if "COMMAND" in guide:
        cind = guide.index("COMMAND")
    elif "CMD" in guide:
        cind = guide.index("CMD")
    for line in plines:
        if not line:
            continue
        comps = line.split()
        ret[comps[pind]] = {"user": comps[uind], "cmd": " ".join(comps[cind:])}
    return ret


def custom():
    """
    Return a custom composite of status data and info for this minion,
    based on the minion config file. An example config like might be::

        status.cpustats.custom: [ 'cpu', 'ctxt', 'btime', 'processes' ]

    Where status refers to status.py, cpustats is the function
    where we get our data, and custom is this function It is followed
    by a list of keys that we want returned.

    This function is meant to replace all_status(), which returns
    anything and everything, which we probably don't want.

    By default, nothing is returned. Warning: Depending on what you
    include, there can be a LOT here!

    CLI Example:

    .. code-block:: bash

        salt '*' status.custom
    """
    ret = {}
    conf = __salt__["config.dot_vals"]("status")
    for key, val in six.iteritems(conf):
        func = "{0}()".format(key.split(".")[1])
        vals = eval(func)  # pylint: disable=W0123

        for item in val:
            ret[item] = vals[item]

    return ret


def uptime():
    """
    Return the uptime for this system.

    .. versionchanged:: 2015.8.9
        The uptime function was changed to return a dictionary of easy-to-read
        key/value pairs containing uptime information, instead of the output
        from a ``cmd.run`` call.

    .. versionchanged:: 2016.11.0
        Support for OpenBSD, FreeBSD, NetBSD, MacOS, and Solaris

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.uptime
    """
    curr_seconds = time.time()

    # Get uptime in seconds
    if salt.utils.platform.is_linux():
        ut_path = "/proc/uptime"
        if not os.path.exists(ut_path):
            raise CommandExecutionError(
                "File {ut_path} was not found.".format(ut_path=ut_path)
            )
        with salt.utils.files.fopen(ut_path) as rfh:
            seconds = int(float(rfh.read().split()[0]))
    elif salt.utils.platform.is_sunos():
        # note: some flavors/versions report the host uptime inside a zone
        #       https://support.oracle.com/epmos/faces/BugDisplay?id=15611584
        res = __salt__["cmd.run_all"]("kstat -p unix:0:system_misc:boot_time")
        if res["retcode"] > 0:
            raise CommandExecutionError("The boot_time kstat was not found.")
        seconds = int(curr_seconds - int(res["stdout"].split()[-1]))
    elif salt.utils.platform.is_openbsd() or salt.utils.platform.is_netbsd():
        bt_data = __salt__["sysctl.get"]("kern.boottime")
        if not bt_data:
            raise CommandExecutionError("Cannot find kern.boottime system parameter")
        seconds = int(curr_seconds - int(bt_data))
    elif salt.utils.platform.is_freebsd() or salt.utils.platform.is_darwin():
        # format: { sec = 1477761334, usec = 664698 } Sat Oct 29 17:15:34 2016
        bt_data = __salt__["sysctl.get"]("kern.boottime")
        if not bt_data:
            raise CommandExecutionError("Cannot find kern.boottime system parameter")
        data = bt_data.split("{")[-1].split("}")[0].strip().replace(" ", "")
        uptime = dict(
            [(k, int(v,)) for k, v in [p.strip().split("=") for p in data.split(",")]]
        )
        seconds = int(curr_seconds - uptime["sec"])
    elif salt.utils.platform.is_aix():
        seconds = _get_boot_time_aix()
    else:
        return __salt__["cmd.run"]("uptime")

    # Setup datetime and timedelta objects
    boot_time = datetime.datetime.utcfromtimestamp(curr_seconds - seconds)
    curr_time = datetime.datetime.utcfromtimestamp(curr_seconds)
    up_time = curr_time - boot_time

    # Construct return information
    ut_ret = {
        "seconds": seconds,
        "since_iso": boot_time.isoformat(),
        "since_t": int(curr_seconds - seconds),
        "days": up_time.days,
        "time": "{0}:{1}".format(up_time.seconds // 3600, up_time.seconds % 3600 // 60),
    }

    if salt.utils.path.which("who"):
        who_cmd = (
            "who" if salt.utils.platform.is_openbsd() else "who -s"
        )  # OpenBSD does not support -s
        ut_ret["users"] = len(__salt__["cmd.run"](who_cmd).split(os.linesep))

    return ut_ret


def loadavg():
    """
    Return the load averages for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.loadavg

        :raises CommandExecutionError: If the system cannot report loadaverages to Python
    """
    if __grains__["kernel"] == "AIX":
        return _aix_loadavg()

    try:
        load_avg = os.getloadavg()
    except AttributeError:
        # Some UNIX-based operating systems do not have os.getloadavg()
        raise salt.exceptions.CommandExecutionError(
            "status.loadavag is not available on your platform"
        )
    return {"1-min": load_avg[0], "5-min": load_avg[1], "15-min": load_avg[2]}


def cpustats():
    """
    Return the CPU stats for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    .. versionchanged:: 2018.3.0
        Added support for OpenBSD

    CLI Example:

    .. code-block:: bash

        salt '*' status.cpustats
    """

    def linux_cpustats():
        """
        linux specific implementation of cpustats
        """
        ret = {}
        try:
            with salt.utils.files.fopen("/proc/stat", "r") as fp_:
                stats = salt.utils.stringutils.to_unicode(fp_.read())
        except IOError:
            pass
        else:
            for line in stats.splitlines():
                if not line:
                    continue
                comps = line.split()
                if comps[0] == "cpu":
                    ret[comps[0]] = {
                        "idle": _number(comps[4]),
                        "iowait": _number(comps[5]),
                        "irq": _number(comps[6]),
                        "nice": _number(comps[2]),
                        "softirq": _number(comps[7]),
                        "steal": _number(comps[8]),
                        "system": _number(comps[3]),
                        "user": _number(comps[1]),
                    }
                elif comps[0] == "intr":
                    ret[comps[0]] = {
                        "total": _number(comps[1]),
                        "irqs": [_number(x) for x in comps[2:]],
                    }
                elif comps[0] == "softirq":
                    ret[comps[0]] = {
                        "total": _number(comps[1]),
                        "softirqs": [_number(x) for x in comps[2:]],
                    }
                else:
                    ret[comps[0]] = _number(comps[1])
        return ret

    def freebsd_cpustats():
        """
        freebsd specific implementation of cpustats
        """
        vmstat = __salt__["cmd.run"]("vmstat -P").splitlines()
        vm0 = vmstat[0].split()
        cpu0loc = vm0.index("cpu0")
        vm1 = vmstat[1].split()
        usloc = vm1.index("us")
        vm2 = vmstat[2].split()
        cpuctr = 0
        ret = {}
        for cpu in vm0[cpu0loc:]:
            ret[cpu] = {
                "us": _number(vm2[usloc + 3 * cpuctr]),
                "sy": _number(vm2[usloc + 1 + 3 * cpuctr]),
                "id": _number(vm2[usloc + 2 + 3 * cpuctr]),
            }
            cpuctr += 1
        return ret

    def sunos_cpustats():
        """
        sunos specific implementation of cpustats
        """
        mpstat = __salt__["cmd.run"]("mpstat 1 2").splitlines()
        fields = mpstat[0].split()
        ret = {}
        for cpu in mpstat:
            if cpu.startswith("CPU"):
                continue
            cpu = cpu.split()
            ret[_number(cpu[0])] = {}
            for i in range(1, len(fields) - 1):
                ret[_number(cpu[0])][fields[i]] = _number(cpu[i])
        return ret

    def aix_cpustats():
        """
        AIX specific implementation of cpustats
        """
        ret = {}
        ret["mpstat"] = []
        procn = None
        fields = []
        for line in __salt__["cmd.run"]("mpstat -a").splitlines():
            if not line:
                continue
            procn = len(ret["mpstat"])
            if line.startswith("System"):
                comps = line.split(":")
                ret["mpstat"].append({})
                ret["mpstat"][procn]["system"] = {}
                cpu_comps = comps[1].split()
                for i in range(0, len(cpu_comps)):
                    cpu_vals = cpu_comps[i].split("=")
                    ret["mpstat"][procn]["system"][cpu_vals[0]] = cpu_vals[1]

            if line.startswith("cpu"):
                fields = line.split()
                continue

            if fields:
                cpustat = line.split()
                ret[_number(cpustat[0])] = {}
                for i in range(1, len(fields) - 1):
                    ret[_number(cpustat[0])][fields[i]] = _number(cpustat[i])

        return ret

    def openbsd_cpustats():
        """
        openbsd specific implementation of cpustats
        """
        systat = __salt__["cmd.run"]("systat -s 2 -B cpu").splitlines()
        fields = systat[3].split()
        ret = {}
        for cpu in systat[4:]:
            cpu_line = cpu.split()
            cpu_idx = cpu_line[0]
            ret[cpu_idx] = {}

            for idx, field in enumerate(fields[1:]):
                ret[cpu_idx][field] = cpu_line[idx + 1]

        return ret

    # dict that return a function that does the right thing per platform
    get_version = {
        "Linux": linux_cpustats,
        "FreeBSD": freebsd_cpustats,
        "OpenBSD": openbsd_cpustats,
        "SunOS": sunos_cpustats,
        "AIX": aix_cpustats,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def meminfo():
    """
    Return the memory info for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    .. versionchanged:: 2018.3.0
        Added support for OpenBSD

    CLI Example:

    .. code-block:: bash

        salt '*' status.meminfo
    """

    def linux_meminfo():
        """
        linux specific implementation of meminfo
        """
        ret = {}
        try:
            with salt.utils.files.fopen("/proc/meminfo", "r") as fp_:
                stats = salt.utils.stringutils.to_unicode(fp_.read())
        except IOError:
            pass
        else:
            for line in stats.splitlines():
                if not line:
                    continue
                comps = line.split()
                comps[0] = comps[0].replace(":", "")
                ret[comps[0]] = {
                    "value": comps[1],
                }
                if len(comps) > 2:
                    ret[comps[0]]["unit"] = comps[2]
        return ret

    def freebsd_meminfo():
        """
        freebsd specific implementation of meminfo
        """
        sysctlvm = __salt__["cmd.run"]("sysctl vm").splitlines()
        sysctlvm = [x for x in sysctlvm if x.startswith("vm")]
        sysctlvm = [x.split(":") for x in sysctlvm]
        sysctlvm = [[y.strip() for y in x] for x in sysctlvm]
        sysctlvm = [x for x in sysctlvm if x[1]]  # If x[1] not empty

        ret = {}
        for line in sysctlvm:
            ret[line[0]] = line[1]
        # Special handling for vm.total as it's especially important
        sysctlvmtot = __salt__["cmd.run"]("sysctl -n vm.vmtotal").splitlines()
        sysctlvmtot = [x for x in sysctlvmtot if x]
        ret["vm.vmtotal"] = sysctlvmtot
        return ret

    def aix_meminfo():
        """
        AIX specific implementation of meminfo
        """
        ret = {}
        ret["svmon"] = []
        ret["vmstat"] = []
        procn = None
        fields = []
        pagesize_flag = False
        for line in __salt__["cmd.run"]("svmon -G").splitlines():
            # Note: svmon is per-system
            #               size       inuse        free         pin     virtual   mmode
            # memory      1048576     1039740        8836      285078      474993     Ded
            # pg space     917504        2574
            #
            #               work        pers        clnt       other
            # pin          248379           0        2107       34592
            # in use       474993           0      564747
            #
            # PageSize   PoolSize       inuse        pgsp         pin     virtual
            # s    4 KB         -      666956        2574       60726      102209
            # m   64 KB         -       23299           0       14022       23299
            if not line:
                continue

            if re.match(r"\s", line):
                # assume fields line
                fields = line.split()
                continue

            if line.startswith("memory") or line.startswith("pin"):
                procn = len(ret["svmon"])
                ret["svmon"].append({})
                comps = line.split()
                ret["svmon"][procn][comps[0]] = {}
                for i in range(0, len(fields)):
                    if len(comps) > i + 1:
                        ret["svmon"][procn][comps[0]][fields[i]] = comps[i + 1]
                continue

            if line.startswith("pg space") or line.startswith("in use"):
                procn = len(ret["svmon"])
                ret["svmon"].append({})
                comps = line.split()
                pg_space = "{0} {1}".format(comps[0], comps[1])
                ret["svmon"][procn][pg_space] = {}
                for i in range(0, len(fields)):
                    if len(comps) > i + 2:
                        ret["svmon"][procn][pg_space][fields[i]] = comps[i + 2]
                continue

            if line.startswith("PageSize"):
                fields = line.split()
                pagesize_flag = False
                continue

            if pagesize_flag:
                procn = len(ret["svmon"])
                ret["svmon"].append({})
                comps = line.split()
                ret["svmon"][procn][comps[0]] = {}
                for i in range(0, len(fields)):
                    if len(comps) > i:
                        ret["svmon"][procn][comps[0]][fields[i]] = comps[i]
                continue

        for line in __salt__["cmd.run"]("vmstat -v").splitlines():
            # Note: vmstat is per-system
            if not line:
                continue

            procn = len(ret["vmstat"])
            ret["vmstat"].append({})
            comps = line.lstrip().split(" ", 1)
            ret["vmstat"][procn][comps[1]] = comps[0]

        return ret

    def openbsd_meminfo():
        """
        openbsd specific implementation of meminfo
        """
        vmstat = __salt__["cmd.run"]("vmstat").splitlines()
        # We're only interested in memory and page values which are printed
        # as subsequent fields.
        fields = [
            "active virtual pages",
            "free list size",
            "page faults",
            "pages reclaimed",
            "pages paged in",
            "pages paged out",
            "pages freed",
            "pages scanned",
        ]
        data = vmstat[2].split()[2:10]
        ret = dict(zip(fields, data))
        return ret

    # dict that return a function that does the right thing per platform
    get_version = {
        "Linux": linux_meminfo,
        "FreeBSD": freebsd_meminfo,
        "OpenBSD": openbsd_meminfo,
        "AIX": aix_meminfo,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def cpuinfo():
    """
    .. versionchanged:: 2016.3.2
        Return the CPU info for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    .. versionchanged:: 2018.3.0
        Added support for NetBSD and OpenBSD

    CLI Example:

    .. code-block:: bash

        salt '*' status.cpuinfo
    """

    def linux_cpuinfo():
        """
        linux specific cpuinfo implementation
        """
        ret = {}
        try:
            with salt.utils.files.fopen("/proc/cpuinfo", "r") as fp_:
                stats = salt.utils.stringutils.to_unicode(fp_.read())
        except IOError:
            pass
        else:
            for line in stats.splitlines():
                if not line:
                    continue
                comps = line.split(":")
                comps[0] = comps[0].strip()
                if comps[0] == "flags":
                    ret[comps[0]] = comps[1].split()
                else:
                    ret[comps[0]] = comps[1].strip()
        return ret

    def bsd_cpuinfo():
        """
        bsd specific cpuinfo implementation
        """
        bsd_cmd = "sysctl hw.model hw.ncpu"
        ret = {}
        if __grains__["kernel"].lower() in ["netbsd", "openbsd"]:
            sep = "="
        else:
            sep = ":"

        for line in __salt__["cmd.run"](bsd_cmd).splitlines():
            if not line:
                continue
            comps = line.split(sep)
            comps[0] = comps[0].strip()
            ret[comps[0]] = comps[1].strip()
        return ret

    def sunos_cpuinfo():
        """
        sunos specific cpuinfo implementation
        """
        ret = {}
        ret["isainfo"] = {}
        for line in __salt__["cmd.run"]("isainfo -x").splitlines():
            # Note: isainfo is per-system and not per-cpu
            # Output Example:
            # amd64: rdrand f16c vmx avx xsave pclmulqdq aes sse4.2 sse4.1 ssse3 popcnt tscp cx16 sse3 sse2 sse fxsr mmx cmov amd_sysc cx8 tsc fpu
            # i386: rdrand f16c vmx avx xsave pclmulqdq aes sse4.2 sse4.1 ssse3 popcnt tscp ahf cx16 sse3 sse2 sse fxsr mmx cmov sep cx8 tsc fpu
            if not line:
                continue
            comps = line.split(":")
            comps[0] = comps[0].strip()
            ret["isainfo"][comps[0]] = sorted(comps[1].strip().split())
        ret["psrinfo"] = []
        procn = None
        for line in __salt__["cmd.run"]("psrinfo -v -p").splitlines():
            # Output Example:
            # The physical processor has 6 cores and 12 virtual processors (0-5 12-17)
            #  The core has 2 virtual processors (0 12)
            #  The core has 2 virtual processors (1 13)
            #  The core has 2 virtual processors (2 14)
            #  The core has 2 virtual processors (3 15)
            #  The core has 2 virtual processors (4 16)
            #  The core has 2 virtual processors (5 17)
            #    x86 (GenuineIntel 306E4 family 6 model 62 step 4 clock 2100 MHz)
            #      Intel(r) Xeon(r) CPU E5-2620 v2 @ 2.10GHz
            # The physical processor has 6 cores and 12 virtual processors (6-11 18-23)
            #  The core has 2 virtual processors (6 18)
            #  The core has 2 virtual processors (7 19)
            #  The core has 2 virtual processors (8 20)
            #  The core has 2 virtual processors (9 21)
            #  The core has 2 virtual processors (10 22)
            #  The core has 2 virtual processors (11 23)
            #    x86 (GenuineIntel 306E4 family 6 model 62 step 4 clock 2100 MHz)
            #      Intel(r) Xeon(r) CPU E5-2620 v2 @ 2.10GHz
            #
            # Output Example 2:
            # The physical processor has 4 virtual processors (0-3)
            #  x86 (GenuineIntel 406D8 family 6 model 77 step 8 clock 2400 MHz)
            #        Intel(r) Atom(tm) CPU  C2558  @ 2.40GHz
            if not line:
                continue
            if line.startswith("The physical processor"):
                procn = len(ret["psrinfo"])
                line = line.split()
                ret["psrinfo"].append({})
                if "cores" in line:
                    ret["psrinfo"][procn]["topology"] = {}
                    ret["psrinfo"][procn]["topology"]["cores"] = _number(line[4])
                    ret["psrinfo"][procn]["topology"]["threads"] = _number(line[7])
                elif "virtual" in line:
                    ret["psrinfo"][procn]["topology"] = {}
                    ret["psrinfo"][procn]["topology"]["threads"] = _number(line[4])
            elif line.startswith(" " * 6):  # 3x2 space indent
                ret["psrinfo"][procn]["name"] = line.strip()
            elif line.startswith(" " * 4):  # 2x2 space indent
                line = line.strip().split()
                ret["psrinfo"][procn]["vendor"] = line[1][1:]
                ret["psrinfo"][procn]["family"] = _number(line[4])
                ret["psrinfo"][procn]["model"] = _number(line[6])
                ret["psrinfo"][procn]["step"] = _number(line[8])
                ret["psrinfo"][procn]["clock"] = "{0} {1}".format(
                    line[10], line[11][:-1]
                )
        return ret

    def aix_cpuinfo():
        """
        AIX  specific cpuinfo implementation
        """
        ret = {}
        ret["prtconf"] = []
        ret["lparstat"] = []
        procn = None
        for line in __salt__["cmd.run"](
            'prtconf | grep -i "Processor"', python_shell=True
        ).splitlines():
            # Note: prtconf is per-system and not per-cpu
            # Output Example:
            # prtconf | grep -i "Processor"
            # Processor Type: PowerPC_POWER7
            # Processor Implementation Mode: POWER 7
            # Processor Version: PV_7_Compat
            # Number Of Processors: 2
            # Processor Clock Speed: 3000 MHz
            #  Model Implementation: Multiple Processor, PCI bus
            #  + proc0                                                           Processor
            #  + proc4                                                           Processor
            if not line:
                continue
            procn = len(ret["prtconf"])
            if line.startswith("Processor") or line.startswith("Number"):
                ret["prtconf"].append({})
                comps = line.split(":")
                comps[0] = comps[0].rstrip()
                ret["prtconf"][procn][comps[0]] = comps[1]
            else:
                continue

        for line in __salt__["cmd.run"](
            'prtconf | grep "CPU"', python_shell=True
        ).splitlines():
            # Note: prtconf is per-system and not per-cpu
            # Output Example:
            # CPU Type: 64-bit
            if not line:
                continue
            procn = len(ret["prtconf"])
            if line.startswith("CPU"):
                ret["prtconf"].append({})
                comps = line.split(":")
                comps[0] = comps[0].rstrip()
                ret["prtconf"][procn][comps[0]] = comps[1]
            else:
                continue

        for line in __salt__["cmd.run"](
            "lparstat -i | grep CPU", python_shell=True
        ).splitlines():
            # Note: lparstat is per-system and not per-cpu
            # Output Example:
            # Online Virtual CPUs                        : 2
            # Maximum Virtual CPUs                       : 2
            # Minimum Virtual CPUs                       : 1
            # Maximum Physical CPUs in system            : 32
            # Active Physical CPUs in system             : 32
            # Active CPUs in Pool                        : 32
            # Shared Physical CPUs in system             : 32
            # Physical CPU Percentage                    : 25.00%
            # Desired Virtual CPUs                       : 2
            if not line:
                continue

            procn = len(ret["lparstat"])
            ret["lparstat"].append({})
            comps = line.split(":")
            comps[0] = comps[0].rstrip()
            ret["lparstat"][procn][comps[0]] = comps[1]

        return ret

    # dict that returns a function that does the right thing per platform
    get_version = {
        "Linux": linux_cpuinfo,
        "FreeBSD": bsd_cpuinfo,
        "NetBSD": bsd_cpuinfo,
        "OpenBSD": bsd_cpuinfo,
        "SunOS": sunos_cpuinfo,
        "AIX": aix_cpuinfo,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def diskstats():
    """
    .. versionchanged:: 2016.3.2
        Return the disk stats for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.diskstats
    """

    def linux_diskstats():
        """
        linux specific implementation of diskstats
        """
        ret = {}
        try:
            with salt.utils.files.fopen("/proc/diskstats", "r") as fp_:
                stats = salt.utils.stringutils.to_unicode(fp_.read())
        except IOError:
            pass
        else:
            for line in stats.splitlines():
                if not line:
                    continue
                comps = line.split()
                ret[comps[2]] = {
                    "major": _number(comps[0]),
                    "minor": _number(comps[1]),
                    "device": _number(comps[2]),
                    "reads_issued": _number(comps[3]),
                    "reads_merged": _number(comps[4]),
                    "sectors_read": _number(comps[5]),
                    "ms_spent_reading": _number(comps[6]),
                    "writes_completed": _number(comps[7]),
                    "writes_merged": _number(comps[8]),
                    "sectors_written": _number(comps[9]),
                    "ms_spent_writing": _number(comps[10]),
                    "io_in_progress": _number(comps[11]),
                    "ms_spent_in_io": _number(comps[12]),
                    "weighted_ms_spent_in_io": _number(comps[13]),
                }
        return ret

    def generic_diskstats():
        """
        generic implementation of diskstats
        note: freebsd and sunos
        """
        ret = {}
        iostat = __salt__["cmd.run"]("iostat -xzd").splitlines()
        header = iostat[1]
        for line in iostat[2:]:
            comps = line.split()
            ret[comps[0]] = {}
            for metric, value in zip(header.split()[1:], comps[1:]):
                ret[comps[0]][metric] = _number(value)
        return ret

    def aix_diskstats():
        """
        AIX specific implementation of diskstats
        """
        ret = {}
        procn = None
        fields = []
        disk_name = ""
        disk_mode = ""
        for line in __salt__["cmd.run"]("iostat -dDV").splitlines():
            # Note: iostat -dDV is per-system
            #
            # System configuration: lcpu=8 drives=1 paths=2 vdisks=2
            #
            # hdisk0          xfer:  %tm_act      bps      tps      bread      bwrtn
            #                          0.0      0.8      0.0        0.0        0.8
            #                read:      rps  avgserv  minserv  maxserv   timeouts      fails
            #                          0.0      2.5      0.3     12.4           0          0
            #               write:      wps  avgserv  minserv  maxserv   timeouts      fails
            #                          0.0      0.3      0.2      0.7           0          0
            #               queue:  avgtime  mintime  maxtime  avgwqsz    avgsqsz     sqfull
            #                          0.3      0.0      5.3      0.0        0.0         0.0
            # --------------------------------------------------------------------------------
            if not line or line.startswith("System") or line.startswith("-----------"):
                continue

            if not re.match(r"\s", line):
                # have new disk
                dsk_comps = line.split(":")
                dsk_firsts = dsk_comps[0].split()
                disk_name = dsk_firsts[0]
                disk_mode = dsk_firsts[1]
                fields = dsk_comps[1].split()
                ret[disk_name] = []

                procn = len(ret[disk_name])
                ret[disk_name].append({})
                ret[disk_name][procn][disk_mode] = {}
                continue

            if ":" in line:
                comps = line.split(":")
                fields = comps[1].split()
                disk_mode = comps[0].lstrip()
                procn = len(ret[disk_name])
                ret[disk_name].append({})
                ret[disk_name][procn][disk_mode] = {}
            else:
                comps = line.split()
                for i in range(0, len(fields)):
                    if len(comps) > i:
                        ret[disk_name][procn][disk_mode][fields[i]] = comps[i]

        return ret

    # dict that return a function that does the right thing per platform
    get_version = {
        "Linux": linux_diskstats,
        "FreeBSD": generic_diskstats,
        "SunOS": generic_diskstats,
        "AIX": aix_diskstats,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def diskusage(*args):
    """
    Return the disk usage for this minion

    Usage::

        salt '*' status.diskusage [paths and/or filesystem types]

    CLI Example:

    .. code-block:: bash

        salt '*' status.diskusage         # usage for all filesystems
        salt '*' status.diskusage / /tmp  # usage for / and /tmp
        salt '*' status.diskusage ext?    # usage for ext[234] filesystems
        salt '*' status.diskusage / ext?  # usage for / and all ext filesystems
    """
    selected = set()
    fstypes = set()
    if not args:
        # select all filesystems
        fstypes.add("*")
    else:
        for arg in args:
            if arg.startswith("/"):
                # select path
                selected.add(arg)
            else:
                # select fstype
                fstypes.add(arg)

    if fstypes:
        # determine which mount points host the specified fstypes
        regex = re.compile(
            "|".join(fnmatch.translate(fstype).format("(%s)") for fstype in fstypes)
        )
        # ifile source of data varies with OS, otherwise all the same
        if __grains__["kernel"] == "Linux":
            try:
                with salt.utils.files.fopen("/proc/mounts", "r") as fp_:
                    ifile = salt.utils.stringutils.to_unicode(fp_.read()).splitlines()
            except OSError:
                return {}
        elif __grains__["kernel"] in ("FreeBSD", "SunOS"):
            ifile = __salt__["cmd.run"]("mount -p").splitlines()
        else:
            raise CommandExecutionError(
                "status.diskusage not yet supported on this platform"
            )

        for line in ifile:
            comps = line.split()
            if __grains__["kernel"] == "SunOS":
                if len(comps) >= 4:
                    mntpt = comps[2]
                    fstype = comps[3]
                    if regex.match(fstype):
                        selected.add(mntpt)
            else:
                if len(comps) >= 3:
                    mntpt = comps[1]
                    fstype = comps[2]
                    if regex.match(fstype):
                        selected.add(mntpt)

    # query the filesystems disk usage
    ret = {}
    for path in selected:
        fsstats = os.statvfs(path)
        blksz = fsstats.f_bsize
        available = fsstats.f_bavail * blksz
        total = fsstats.f_blocks * blksz
        ret[path] = {"available": available, "total": total}
    return ret


def vmstats():
    """
    .. versionchanged:: 2016.3.2
        Return the virtual memory stats for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.vmstats
    """

    def linux_vmstats():
        """
        linux specific implementation of vmstats
        """
        ret = {}
        try:
            with salt.utils.files.fopen("/proc/vmstat", "r") as fp_:
                stats = salt.utils.stringutils.to_unicode(fp_.read())
        except IOError:
            pass
        else:
            for line in stats.splitlines():
                if not line:
                    continue
                comps = line.split()
                ret[comps[0]] = _number(comps[1])
        return ret

    def generic_vmstats():
        """
        generic implementation of vmstats
        note: works on FreeBSD, SunOS and OpenBSD (possibly others)
        """
        ret = {}
        for line in __salt__["cmd.run"]("vmstat -s").splitlines():
            comps = line.split()
            if comps[0].isdigit():
                ret[" ".join(comps[1:])] = _number(comps[0].strip())
        return ret

    # dict that returns a function that does the right thing per platform
    get_version = {
        "Linux": linux_vmstats,
        "FreeBSD": generic_vmstats,
        "OpenBSD": generic_vmstats,
        "SunOS": generic_vmstats,
        "AIX": generic_vmstats,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def nproc():
    """
    Return the number of processing units available on this system

    .. versionchanged:: 2016.11.4
        Added support for AIX

    .. versionchanged:: 2018.3.0
        Added support for Darwin, FreeBSD and OpenBSD

    CLI Example:

    .. code-block:: bash

        salt '*' status.nproc
    """

    def linux_nproc():
        """
        linux specific implementation of nproc
        """
        try:
            return _number(__salt__["cmd.run"]("nproc").strip())
        except ValueError:
            return 0

    def generic_nproc():
        """
        generic implementation of nproc
        """
        ncpu_data = __salt__["sysctl.get"]("hw.ncpu")
        if not ncpu_data:
            # We need at least one CPU to run
            return 1
        else:
            return _number(ncpu_data)

    # dict that returns a function that does the right thing per platform
    get_version = {
        "Linux": linux_nproc,
        "Darwin": generic_nproc,
        "FreeBSD": generic_nproc,
        "OpenBSD": generic_nproc,
        "AIX": _aix_nproc,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def netstats():
    """
    Return the network stats for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    .. versionchanged:: 2018.3.0
        Added support for OpenBSD

    CLI Example:

    .. code-block:: bash

        salt '*' status.netstats
    """

    def linux_netstats():
        """
        linux specific netstats implementation
        """
        ret = {}
        try:
            with salt.utils.files.fopen("/proc/net/netstat", "r") as fp_:
                stats = salt.utils.stringutils.to_unicode(fp_.read())
        except IOError:
            pass
        else:
            headers = [""]
            for line in stats.splitlines():
                if not line:
                    continue
                comps = line.split()
                if comps[0] == headers[0]:
                    index = len(headers) - 1
                    row = {}
                    for field in range(index):
                        if field < 1:
                            continue
                        else:
                            row[headers[field]] = _number(comps[field])
                    rowname = headers[0].replace(":", "")
                    ret[rowname] = row
                else:
                    headers = comps
        return ret

    def freebsd_netstats():
        return bsd_netstats()

    def bsd_netstats():
        """
        bsd specific netstats implementation
        """
        ret = {}
        for line in __salt__["cmd.run"]("netstat -s").splitlines():
            if line.startswith("\t\t"):
                continue  # Skip, too detailed
            if not line.startswith("\t"):
                key = line.split()[0].replace(":", "")
                ret[key] = {}
            else:
                comps = line.split()
                if comps[0].isdigit():
                    ret[key][" ".join(comps[1:])] = comps[0]
        return ret

    def sunos_netstats():
        """
        sunos specific netstats implementation
        """
        ret = {}
        for line in __salt__["cmd.run"]("netstat -s").splitlines():
            line = line.replace("=", " = ").split()
            if len(line) > 6:
                line.pop(0)
            if "=" in line:
                if len(line) >= 3:
                    if line[2].isdigit() or line[2][0] == "-":
                        line[2] = _number(line[2])
                    ret[line[0]] = line[2]
                if len(line) >= 6:
                    if line[5].isdigit() or line[5][0] == "-":
                        line[5] = _number(line[5])
                    ret[line[3]] = line[5]
        return ret

    def aix_netstats():
        """
        AIX specific netstats implementation
        """
        ret = {}
        fields = []
        procn = None
        proto_name = None
        for line in __salt__["cmd.run"]("netstat -s").splitlines():
            if not line:
                continue

            if not re.match(r"\s", line) and ":" in line:
                comps = line.split(":")
                proto_name = comps[0]
                ret[proto_name] = []
                procn = len(ret[proto_name])
                ret[proto_name].append({})
                continue
            else:
                comps = line.split()
                comps[0] = comps[0].strip()
                if comps[0].isdigit():
                    ret[proto_name][procn][" ".join(comps[1:])] = _number(comps[0])
                else:
                    continue

        return ret

    # dict that returns a function that does the right thing per platform
    get_version = {
        "Linux": linux_netstats,
        "FreeBSD": bsd_netstats,
        "OpenBSD": bsd_netstats,
        "SunOS": sunos_netstats,
        "AIX": aix_netstats,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def netdev():
    """
    .. versionchanged:: 2016.3.2
        Return the network device stats for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.netdev
    """

    def linux_netdev():
        """
        linux specific implementation of netdev
        """
        ret = {}
        try:
            with salt.utils.files.fopen("/proc/net/dev", "r") as fp_:
                stats = salt.utils.stringutils.to_unicode(fp_.read())
        except IOError:
            pass
        else:
            for line in stats.splitlines():
                if not line:
                    continue
                if line.find(":") < 0:
                    continue
                comps = line.split()
                # Fix lines like eth0:9999..'
                comps[0] = line.split(":")[0].strip()
                # Support lines both like eth0:999 and eth0: 9999
                comps.insert(1, line.split(":")[1].strip().split()[0])
                ret[comps[0]] = {
                    "iface": comps[0],
                    "rx_bytes": _number(comps[2]),
                    "rx_compressed": _number(comps[8]),
                    "rx_drop": _number(comps[5]),
                    "rx_errs": _number(comps[4]),
                    "rx_fifo": _number(comps[6]),
                    "rx_frame": _number(comps[7]),
                    "rx_multicast": _number(comps[9]),
                    "rx_packets": _number(comps[3]),
                    "tx_bytes": _number(comps[10]),
                    "tx_carrier": _number(comps[16]),
                    "tx_colls": _number(comps[15]),
                    "tx_compressed": _number(comps[17]),
                    "tx_drop": _number(comps[13]),
                    "tx_errs": _number(comps[12]),
                    "tx_fifo": _number(comps[14]),
                    "tx_packets": _number(comps[11]),
                }
        return ret

    def freebsd_netdev():
        """
        freebsd specific implementation of netdev
        """
        _dict_tree = lambda: collections.defaultdict(_dict_tree)
        ret = _dict_tree()
        netstat = __salt__["cmd.run"]("netstat -i -n -4 -b -d").splitlines()
        netstat += __salt__["cmd.run"]("netstat -i -n -6 -b -d").splitlines()[1:]
        header = netstat[0].split()
        for line in netstat[1:]:
            comps = line.split()
            for i in range(4, 13):  # The columns we want
                ret[comps[0]][comps[2]][comps[3]][header[i]] = _number(comps[i])
        return ret

    def sunos_netdev():
        """
        sunos specific implementation of netdev
        """
        ret = {}
        ##NOTE: we cannot use hwaddr_interfaces here, so we grab both ip4 and ip6
        for dev in __grains__["ip4_interfaces"].keys() + __grains__["ip6_interfaces"]:
            # fetch device info
            netstat_ipv4 = __salt__["cmd.run"](
                "netstat -i -I {dev} -n -f inet".format(dev=dev)
            ).splitlines()
            netstat_ipv6 = __salt__["cmd.run"](
                "netstat -i -I {dev} -n -f inet6".format(dev=dev)
            ).splitlines()

            # prepare data
            netstat_ipv4[0] = netstat_ipv4[0].split()
            netstat_ipv4[1] = netstat_ipv4[1].split()
            netstat_ipv6[0] = netstat_ipv6[0].split()
            netstat_ipv6[1] = netstat_ipv6[1].split()

            # add data
            ret[dev] = {}
            for i in range(len(netstat_ipv4[0]) - 1):
                if netstat_ipv4[0][i] == "Name":
                    continue
                if netstat_ipv4[0][i] in ["Address", "Net/Dest"]:
                    ret[dev][
                        "IPv4 {field}".format(field=netstat_ipv4[0][i])
                    ] = netstat_ipv4[1][i]
                else:
                    ret[dev][netstat_ipv4[0][i]] = _number(netstat_ipv4[1][i])
            for i in range(len(netstat_ipv6[0]) - 1):
                if netstat_ipv6[0][i] == "Name":
                    continue
                if netstat_ipv6[0][i] in ["Address", "Net/Dest"]:
                    ret[dev][
                        "IPv6 {field}".format(field=netstat_ipv6[0][i])
                    ] = netstat_ipv6[1][i]
                else:
                    ret[dev][netstat_ipv6[0][i]] = _number(netstat_ipv6[1][i])

        return ret

    def aix_netdev():
        """
        AIX specific implementation of netdev
        """
        ret = {}
        fields = []
        procn = None
        for dev in (
            __grains__["ip4_interfaces"].keys() + __grains__["ip6_interfaces"].keys()
        ):
            # fetch device info
            # root@la68pp002_pub:/opt/salt/lib/python2.7/site-packages/salt/modules# netstat -i -n -I en0 -f inet6
            # Name  Mtu   Network     Address            Ipkts Ierrs    Opkts Oerrs  Coll
            # en0   1500  link#3      e2.eb.32.42.84.c 10029668     0   446490     0     0
            # en0   1500  172.29.128  172.29.149.95    10029668     0   446490     0     0
            # root@la68pp002_pub:/opt/salt/lib/python2.7/site-packages/salt/modules# netstat -i -n -I en0 -f inet6
            # Name  Mtu   Network     Address            Ipkts Ierrs    Opkts Oerrs  Coll
            # en0   1500  link#3      e2.eb.32.42.84.c 10029731     0   446499     0     0

            netstat_ipv4 = __salt__["cmd.run"](
                "netstat -i -n -I {dev} -f inet".format(dev=dev)
            ).splitlines()
            netstat_ipv6 = __salt__["cmd.run"](
                "netstat -i -n -I {dev} -f inet6".format(dev=dev)
            ).splitlines()

            # add data
            ret[dev] = []

            for line in netstat_ipv4:
                if line.startswith("Name"):
                    fields = line.split()
                    continue

                comps = line.split()
                if len(comps) < 3:
                    raise CommandExecutionError(
                        "Insufficent data returned by command to process '{0}'".format(
                            line
                        )
                    )

                if comps[2].startswith("link"):
                    continue

                procn = len(ret[dev])
                ret[dev].append({})
                ret[dev][procn]["ipv4"] = {}
                for i in range(1, len(fields)):
                    if len(comps) > i:
                        ret[dev][procn]["ipv4"][fields[i]] = comps[i]

            for line in netstat_ipv6:
                if line.startswith("Name"):
                    fields = line.split()
                    continue

                comps = line.split()
                if len(comps) < 3:
                    raise CommandExecutionError(
                        "Insufficent data returned by command to process '{0}'".format(
                            line
                        )
                    )

                if comps[2].startswith("link"):
                    continue

                procn = len(ret[dev])
                ret[dev].append({})
                ret[dev][procn]["ipv6"] = {}
                for i in range(1, len(fields)):
                    if len(comps) > i:
                        ret[dev][procn]["ipv6"][fields[i]] = comps[i]

        return ret

    # dict that returns a function that does the right thing per platform
    get_version = {
        "Linux": linux_netdev,
        "FreeBSD": freebsd_netdev,
        "SunOS": sunos_netdev,
        "AIX": aix_netdev,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def w():  # pylint: disable=C0103
    """
    Return a list of logged in users for this minion, using the w command

    CLI Example:

    .. code-block:: bash

        salt '*' status.w
    """

    def linux_w():
        """
        Linux specific implementation for w
        """
        user_list = []
        users = __salt__["cmd.run"]("w -fh").splitlines()
        for row in users:
            if not row:
                continue
            comps = row.split()
            rec = {
                "idle": comps[3],
                "jcpu": comps[4],
                "login": comps[2],
                "pcpu": comps[5],
                "tty": comps[1],
                "user": comps[0],
                "what": " ".join(comps[6:]),
            }
            user_list.append(rec)
        return user_list

    def bsd_w():
        """
        Generic BSD implementation for w
        """
        user_list = []
        users = __salt__["cmd.run"]("w -h").splitlines()
        for row in users:
            if not row:
                continue
            comps = row.split()
            rec = {
                "from": comps[2],
                "idle": comps[4],
                "login": comps[3],
                "tty": comps[1],
                "user": comps[0],
                "what": " ".join(comps[5:]),
            }
            user_list.append(rec)
        return user_list

    # dict that returns a function that does the right thing per platform
    get_version = {
        "Darwin": bsd_w,
        "FreeBSD": bsd_w,
        "Linux": linux_w,
        "OpenBSD": bsd_w,
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def all_status():
    """
    Return a composite of all status data and info for this minion.
    Warning: There is a LOT here!

    CLI Example:

    .. code-block:: bash

        salt '*' status.all_status
    """
    return {
        "cpuinfo": cpuinfo(),
        "cpustats": cpustats(),
        "diskstats": diskstats(),
        "diskusage": diskusage(),
        "loadavg": loadavg(),
        "meminfo": meminfo(),
        "netdev": netdev(),
        "netstats": netstats(),
        "uptime": uptime(),
        "vmstats": vmstats(),
        "w": w(),
    }


def pid(sig):
    """
    Return the PID or an empty string if the process is running or not.
    Pass a signature to use to find the process via ps.  Note you can pass
    a Python-compatible regular expression to return all pids of
    processes matching the regexp.

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.pid <sig>
    """
    my_pid = str(os.getpid())
    cmd = __grains__["ps"]
    output = __salt__["cmd.run_stdout"](cmd, python_shell=True)

    pids = ""
    for line in output.splitlines():
        if my_pid in line:
            continue
        if re.search(sig, line):
            if pids:
                pids += "\n"
            pids += line.split()[1]

    return pids


def version():
    """
    Return the system version for this minion

    .. versionchanged:: 2016.11.4
        Added support for AIX

    .. versionchanged:: 2018.3.0
        Added support for OpenBSD

    CLI Example:

    .. code-block:: bash

        salt '*' status.version
    """

    def linux_version():
        """
        linux specific implementation of version
        """
        try:
            with salt.utils.files.fopen("/proc/version", "r") as fp_:
                return salt.utils.stringutils.to_unicode(fp_.read()).strip()
        except IOError:
            return {}

    def bsd_version():
        """
        bsd specific implementation of version
        """
        return __salt__["cmd.run"]("sysctl -n kern.version")

    # dict that returns a function that does the right thing per platform
    get_version = {
        "Linux": linux_version,
        "FreeBSD": bsd_version,
        "OpenBSD": bsd_version,
        "AIX": lambda: __salt__["cmd.run"]("oslevel -s"),
    }

    errmsg = "This method is unsupported on the current operating system!"
    return get_version.get(__grains__["kernel"], lambda: errmsg)()


def master(master=None, connected=True):
    """
    .. versionadded:: 2014.7.0

    Return the connection status with master. Fire an event if the
    connection to master is not as expected. This function is meant to be
    run via a scheduled job from the minion. If master_ip is an FQDN/Hostname,
    it must be resolvable to a valid IPv4 address.

    .. versionchanged:: 2016.11.4
        Added support for AIX

    CLI Example:

    .. code-block:: bash

        salt '*' status.master
    """
    master_ips = None

    if master:
        master_ips = salt.utils.network.host_to_ips(master)

    if not master_ips:
        return

    master_connection_status = False
    port = __salt__["config.get"]("publish_port", default=4505)
    connected_ips = salt.utils.network.remote_port_tcp(port)

    # Get connection status for master
    for master_ip in master_ips:
        if master_ip in connected_ips:
            master_connection_status = True
            break

    # Connection to master is not as expected
    if master_connection_status is not connected:
        with salt.utils.event.get_event("minion", opts=__opts__, listen=False) as event:
            if master_connection_status:
                event.fire_event(
                    {"master": master}, salt.minion.master_event(type="connected")
                )
            else:
                event.fire_event(
                    {"master": master}, salt.minion.master_event(type="disconnected")
                )

    return master_connection_status


def ping_master(master):
    """
    .. versionadded:: 2016.3.0

    Sends ping request to the given master. Fires '__master_failback' event on success.
    Returns bool result.

    CLI Example:

    .. code-block:: bash

        salt '*' status.ping_master localhost
    """
    if master is None or master == "":
        return False

    opts = copy.deepcopy(__opts__)
    opts["master"] = master
    if "master_ip" in opts:  # avoid 'master ip changed' warning
        del opts["master_ip"]
    opts.update(salt.minion.prep_ip_port(opts))
    try:
        opts.update(salt.minion.resolve_dns(opts, fallback=False))
    except Exception:  # pylint: disable=broad-except
        return False

    timeout = opts.get("auth_timeout", 60)
    load = {"cmd": "ping"}

    result = False
    with salt.transport.client.ReqChannel.factory(opts, crypt="clear") as channel:
        try:
            payload = channel.send(load, tries=0, timeout=timeout)
            result = True
        except Exception as e:  # pylint: disable=broad-except
            pass

        if result:
            with salt.utils.event.get_event(
                "minion", opts=__opts__, listen=False
            ) as event:
                event.fire_event(
                    {"master": master}, salt.minion.master_event(type="failback")
                )

        return result


def proxy_reconnect(proxy_name, opts=None):
    """
    Forces proxy minion reconnection when not alive.

    proxy_name
        The virtual name of the proxy module.

    opts: None
        Opts dictionary. Not intended for CLI usage.

    CLI Example:

        salt '*' status.proxy_reconnect rest_sample
    """

    if not opts:
        opts = __opts__

    if "proxy" not in opts:
        return False  # fail

    proxy_keepalive_fn = proxy_name + ".alive"
    if proxy_keepalive_fn not in __proxy__:
        return False  # fail

    is_alive = __proxy__[proxy_keepalive_fn](opts)
    if not is_alive:
        minion_id = opts.get("proxyid", "") or opts.get("id", "")
        log.info("%s (%s proxy) is down. Restarting.", minion_id, proxy_name)
        __proxy__[proxy_name + ".shutdown"](opts)  # safely close connection
        __proxy__[proxy_name + ".init"](opts)  # reopen connection
        log.debug("Restarted %s (%s proxy)!", minion_id, proxy_name)

    return True  # success


def time_(format="%A, %d. %B %Y %I:%M%p"):
    """
    .. versionadded:: 2016.3.0

    Return the current time on the minion,
    formatted based on the format parameter.

    Default date format: Monday, 27. July 2015 07:55AM

    CLI Example:

    .. code-block:: bash

        salt '*' status.time

        salt '*' status.time '%s'

    """

    dt = datetime.datetime.today()
    return dt.strftime(format)
