"""
Module for returning various status data about a minion.
These data can be useful for compiling into stats later.
"""

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

import salt.channel
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

log = logging.getLogger(__file__)

__virtualname__ = "status"

# 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#*:}))
    """
    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"]
    match = re.match(r"\s*(?:(\d+)-)?(?:(\d\d):)?(\d\d):(\d\d)\s*", bt_time)
    if not match:
        raise CommandExecutionError("Unexpected time format.")

    groups = match.groups(default="00")
    boot_secs = (
        _number(groups[0]) * 86400
        + _number(groups[1]) * 3600
        + _number(groups[2]) * 60
        + _number(groups[3])
    )
    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 conf.items():
        func = ".".join(key.split(".")[:-1])
        vals = {}
        if func != "status.custom":
            try:
                vals = __salt__[func]()
                for item in val:
                    try:
                        ret[item] = vals[item]
                    except KeyError:
                        log.warning("val %s not in return of %s", item, func)
                        ret[item] = "UNKNOWN"
            except KeyError:
                log.warning("custom status %s isn't loaded", func)

    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 = {
            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": "{}:{}".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 OSError:
            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 comp in cpu_comps:
                    cpu_vals = comp.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,
        "Junos": 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 OSError:
            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 idx, field in enumerate(fields):
                    if len(comps) > idx + 1:
                        ret["svmon"][procn][comps[0]][field] = comps[idx + 1]
                continue

            if line.startswith("pg space") or line.startswith("in use"):
                procn = len(ret["svmon"])
                ret["svmon"].append({})
                comps = line.split()
                pg_space = "{} {}".format(comps[0], comps[1])
                ret["svmon"][procn][pg_space] = {}
                for idx, field in enumerate(fields):
                    if len(comps) > idx + 2:
                        ret["svmon"][procn][pg_space][field] = comps[idx + 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 idx, field in enumerate(fields):
                    if len(comps) > idx:
                        ret["svmon"][procn][comps[0]][field] = comps[idx]
                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,
        "Junos": 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 OSError:
            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"] = "{} {}".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,
        "Junos": 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 OSError:
            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 idx, field in enumerate(fields):
                    if len(comps) > idx:
                        ret[disk_name][procn][disk_mode][field] = comps[idx]

        return ret

    # dict that return a function that does the right thing per platform
    get_version = {
        "Linux": linux_diskstats,
        "FreeBSD": generic_diskstats,
        "Junos": 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 OSError:
            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,
        "Junos": 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,
        "Junos": 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 OSError:
            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,
        "Junos": 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 OSError:
            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
        """

        def _dict_tree():
            return 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 itertools.chain(
            __grains__["ip4_interfaces"].keys(), __grains__["ip6_interfaces"].keys()
        ):
            # 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 val in netstat_ipv4[0][:-1]:
                if val == "Name":
                    continue
                if val in ["Address", "Net/Dest"]:
                    ret[dev]["IPv4 {field}".format(field=val)] = val
                else:
                    ret[dev][val] = _number(val)
            for val in netstat_ipv6[0][:-1]:
                if val == "Name":
                    continue
                if val in ["Address", "Net/Dest"]:
                    ret[dev]["IPv6 {field}".format(field=val)] = val
                else:
                    ret[dev][val] = _number(val)

        return ret

    def aix_netdev():
        """
        AIX specific implementation of netdev
        """
        ret = {}
        fields = []
        procn = None
        for dev in itertools.chain(
            __grains__["ip4_interfaces"].keys(), __grains__["ip6_interfaces"].keys()
        ):
            # fetch device info
            # root@la68pp002_pub:# netstat -i -n -I en0 -f inet
            # 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:# 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 '{}'".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 '{}'".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,
        "Junos": 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,
        "Junos": 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 OSError:
            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,
        "Junos": 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.channel.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:

    .. code-block:: bash

        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

    chk_reboot_active_key = proxy_name + ".get_reboot_active"
    if chk_reboot_active_key in __proxy__ and __proxy__[chk_reboot_active_key]():
        # if rebooting or shutting down, don't run proxy_reconnect
        # it interferes with the connection and disrupts the shutdown/reboot
        # especially
        minion_id = opts.get("proxyid", "") or opts.get("id", "")
        log.info(
            "%s (%s proxy) is rebooting or shutting down. Don't probe connection.",
            minion_id,
            proxy_name,
        )
        return True

    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)
