salt/modules/status.py
# -*- 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 datetime
import os
import re
import logging
import fnmatch
import collections
import copy
import time
import logging
# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import range # pylint: disable=import-error,no-name-in-module,redefined-builtin
# 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.ext.six.moves import zip
from salt.exceptions import CommandExecutionError
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>
'''
cmd = __grains__['ps']
output = __salt__['cmd.run_stdout'](cmd, python_shell=True)
pids = ''
for line in output.splitlines():
if 'status.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:
return False
timeout = opts.get('auth_timeout', 60)
load = {'cmd': 'ping'}
result = False
channel = salt.transport.client.ReqChannel.factory(opts, crypt='clear')
try:
payload = channel.send(load, tries=0, timeout=timeout)
result = True
except Exception as e:
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)