mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 16:45:27 +00:00
Merge pull request #29669 from jirikotlin/develop
checkrestart functionality
This commit is contained in:
commit
5a476c5dc3
385
salt/modules/restartcheck.py
Normal file
385
salt/modules/restartcheck.py
Normal file
@ -0,0 +1,385 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
checkrestart functionality for Debian and Red Hat Based systems
|
||||
|
||||
Identifies services (processes) that are linked against deleted files (for example after downloading an updated
|
||||
binary of a shared library).
|
||||
|
||||
Based on checkrestart script from debian-goodies (written by Matt Zimmerman for the Debian GNU/Linux distribution,
|
||||
https://packages.debian.org/debian-goodies) and psdel by Sam Morris.
|
||||
|
||||
:codeauthor: Jiri Kotlin <jiri.kotlin@ultimum.io>
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import python libs
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Import salt libs
|
||||
import salt.utils
|
||||
|
||||
HAS_PSUTIL = False
|
||||
try:
|
||||
import psutil
|
||||
HAS_PSUTIL = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only run this module if the psutil python module is installed (package python-psutil).
|
||||
'''
|
||||
return HAS_PSUTIL
|
||||
|
||||
|
||||
def _valid_deleted_file(path):
|
||||
'''
|
||||
Filters file path against unwanted directories and decides whether file is marked as deleted.
|
||||
|
||||
Returns:
|
||||
True if file is desired deleted file, else False.
|
||||
|
||||
Args:
|
||||
path: A string - path to file
|
||||
'''
|
||||
ret = False
|
||||
if path.endswith(' (deleted)'):
|
||||
ret = True
|
||||
if re.compile(r"\(path inode=[0-9]+\)$").search(path):
|
||||
ret = True
|
||||
# We don't care about log files
|
||||
if path.startswith('/var/log/') or path.startswith('/var/local/log/'):
|
||||
ret = False
|
||||
# Or about files under temporary locations
|
||||
if path.startswith('/var/run/') or path.startswith('/var/local/run/'):
|
||||
ret = False
|
||||
# Or about files under /tmp
|
||||
if path.startswith('/tmp/'):
|
||||
ret = False
|
||||
# Or about files under /dev/shm
|
||||
if path.startswith('/dev/shm/'):
|
||||
ret = False
|
||||
# Or about files under /run
|
||||
if path.startswith('/run/'):
|
||||
ret = False
|
||||
# Or about files under /drm
|
||||
if path.startswith('/drm'):
|
||||
ret = False
|
||||
# Or about files under /var/tmp and /var/local/tmp
|
||||
if path.startswith('/var/tmp/') or path.startswith('/var/local/tmp/'):
|
||||
ret = False
|
||||
# Or /dev/zero
|
||||
if path.startswith('/dev/zero'):
|
||||
ret = False
|
||||
# Or /dev/pts (used by gpm)
|
||||
if path.startswith('/dev/pts/'):
|
||||
ret = False
|
||||
# Or /usr/lib/locale
|
||||
if path.startswith('/usr/lib/locale/'):
|
||||
ret = False
|
||||
# Skip files from the user's home directories
|
||||
# many processes hold temporafy files there
|
||||
if path.startswith('/home/'):
|
||||
ret = False
|
||||
# Skip automatically generated files
|
||||
if path.endswith('icon-theme.cache'):
|
||||
ret = False
|
||||
# Skip font files
|
||||
if path.startswith('/var/cache/fontconfig/'):
|
||||
ret = False
|
||||
# Skip Nagios Spool
|
||||
if path.startswith('/var/lib/nagios3/spool/'):
|
||||
ret = False
|
||||
# Skip nagios spool files
|
||||
if path.startswith('/var/lib/nagios3/spool/checkresults/'):
|
||||
ret = False
|
||||
# Skip Postgresql files
|
||||
if path.startswith('/var/lib/postgresql/'):
|
||||
ret = False
|
||||
# Skip VDR lib files
|
||||
if path.startswith('/var/lib/vdr/'):
|
||||
ret = False
|
||||
# Skip Aio files found in MySQL servers
|
||||
if path.startswith('/[aio]'):
|
||||
ret = False
|
||||
return ret
|
||||
|
||||
|
||||
def _deleted_files():
|
||||
'''
|
||||
Iterates over /proc/PID/maps and /proc/PID/fd links and returns list of desired deleted files.
|
||||
|
||||
Returns:
|
||||
List of deleted files to analyze, False on failure.
|
||||
|
||||
'''
|
||||
deleted_files = []
|
||||
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
pinfo = proc.as_dict(attrs=['pid', 'name'])
|
||||
try:
|
||||
maps = salt.utils.fopen('/proc/{0}/maps'.format(pinfo['pid']))
|
||||
dirpath = '/proc/' + str(pinfo['pid']) + '/fd/'
|
||||
listdir = os.listdir(dirpath)
|
||||
except (OSError, IOError):
|
||||
return False
|
||||
|
||||
# /proc/PID/maps
|
||||
maplines = maps.readlines()
|
||||
maps.close()
|
||||
mapline = re.compile(r'^[\da-f]+-[\da-f]+ [r-][w-][x-][sp-] '
|
||||
r'[\da-f]+ [\da-f]{2}:[\da-f]{2} (\d+) *(.+)( \(deleted\))?\n$')
|
||||
|
||||
for line in maplines:
|
||||
matched = mapline.match(line)
|
||||
if matched:
|
||||
path = matched.group(2)
|
||||
if file:
|
||||
if _valid_deleted_file(path):
|
||||
val = (pinfo['name'], pinfo['pid'], path[0:-10])
|
||||
if val not in deleted_files:
|
||||
deleted_files.append(val)
|
||||
|
||||
# /proc/PID/fd
|
||||
try:
|
||||
for link in listdir:
|
||||
path = dirpath + link
|
||||
readlink = os.readlink(path)
|
||||
filenames = []
|
||||
|
||||
if os.path.isfile(readlink):
|
||||
filenames.append(readlink)
|
||||
elif os.path.isdir(readlink) and readlink != '/':
|
||||
for root, dummy_dirs, files in os.walk(readlink, followlinks=True):
|
||||
for name in files:
|
||||
filenames.append(os.path.join(root, name))
|
||||
|
||||
for filename in filenames:
|
||||
if _valid_deleted_file(filename):
|
||||
val = (pinfo['name'], pinfo['pid'], filename)
|
||||
if val not in deleted_files:
|
||||
deleted_files.append(val)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
|
||||
return deleted_files
|
||||
|
||||
|
||||
def _format_output(kernel_restart, packages, verbose, restartable, nonrestartable, restartservicecommands,
|
||||
restartinitcommands):
|
||||
'''
|
||||
Formats the output of the restartcheck module.
|
||||
|
||||
Returns:
|
||||
String - formatted output.
|
||||
|
||||
Args:
|
||||
kernel_restart: indicates that newer kernel is instaled
|
||||
packages: list of packages that should be restarted
|
||||
verbose: enables extensive output
|
||||
restartable: list of restartable packages
|
||||
nonrestartable: list of non-restartable packages
|
||||
restartservicecommands: list of commands to restart services
|
||||
restartinitcommands: list of commands to restart init.d scripts
|
||||
|
||||
'''
|
||||
ret = ''
|
||||
if kernel_restart:
|
||||
ret = str(kernel_restart) + '\n\n'
|
||||
|
||||
ret += "Found {0} processes using old versions of upgraded files.\n".format(len(packages))
|
||||
|
||||
if not verbose:
|
||||
ret += "These are the packages:\n"
|
||||
packages = restartable + nonrestartable
|
||||
for package in packages:
|
||||
ret += package + '\n'
|
||||
else:
|
||||
if len(restartable) > 0:
|
||||
ret += "Of these, {0} seem to contain systemd service definitions or init scripts " \
|
||||
"which can be used to restart them:\n".format(len(restartable))
|
||||
for package in restartable:
|
||||
ret += package + ':\n'
|
||||
for program in packages[package]['processes']:
|
||||
ret += program + '\n'
|
||||
|
||||
if len(restartservicecommands) > 0:
|
||||
ret += "\n\nThese are the systemd services:\n"
|
||||
ret += '\n'.join(restartservicecommands)
|
||||
|
||||
if len(restartinitcommands) > 0:
|
||||
ret += "\n\nThese are the initd scripts:\n"
|
||||
ret += '\n'.join(restartinitcommands)
|
||||
|
||||
if len(nonrestartable) > 0:
|
||||
ret += "\n\nThese processes {0} do not seem to have an associated init script " \
|
||||
"to restart them:\n".format(len(nonrestartable))
|
||||
for package in nonrestartable:
|
||||
ret += package + ':\n'
|
||||
for program in packages[package]['processes']:
|
||||
ret += program + '\n'
|
||||
return ret
|
||||
|
||||
|
||||
def restartcheck(ignorelist=None, blacklist=None, excludepid=None, verbose=True):
|
||||
'''
|
||||
Analyzes files openeded by running processes and seeks for packages which need to be restarted.
|
||||
|
||||
Args:
|
||||
ignorelist: string or list of packages to be ignored
|
||||
blacklist: string or list of file paths to be ignored
|
||||
excludepid: string or list of process IDs to be ignored
|
||||
verbose: boolean, enables extensive output
|
||||
|
||||
Returns:
|
||||
True if no packages for restart found.
|
||||
False on failure.
|
||||
String with checkrestart output if some package seems to need to be restarted.
|
||||
|
||||
.. versionadded:: 2015.8.3
|
||||
|
||||
CLI Example:
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' restartcheck.restartcheck
|
||||
'''
|
||||
if __grains__.get('os_family') == 'Debian':
|
||||
cmd_pkg_query = 'dpkg-query --listfiles '
|
||||
systemd_folder = '/lib/systemd/system/'
|
||||
systemd = '/bin/systemd'
|
||||
kernel = """dpkg --get-selections | grep linux-image | """ \
|
||||
"""perl -pe 's/^linux-image-(\\S+).*/$1/' | tail -2 | head -1"""
|
||||
elif __grains__.get('os_family') == 'RedHat':
|
||||
cmd_pkg_query = 'repoquery -l '
|
||||
systemd_folder = '/usr/lib/systemd/system/'
|
||||
systemd = '/usr/bin/systemctl'
|
||||
kernel = """rpm -q --last kernel | perl -pe 's/^kernel-(\\S+).*/$1/' | head -1"""
|
||||
else:
|
||||
return {'result': False, 'comment': 'Only available on Debian and Red Hat based systems.'}
|
||||
|
||||
# Check kernel versions
|
||||
kernel_last = __salt__['cmd.run'](kernel, python_shell=True)
|
||||
kernel_current = __salt__['cmd.run']('uname -r')
|
||||
if kernel_current != kernel_last:
|
||||
kernel_restart = 'Kernel outdated - current: {0}, last installed: {1}'.format(kernel_current, kernel_last)
|
||||
else:
|
||||
kernel_restart = False
|
||||
|
||||
packages = {}
|
||||
|
||||
if ignorelist:
|
||||
if not isinstance(ignorelist, list):
|
||||
ignorelist = [ignorelist]
|
||||
else:
|
||||
ignorelist = ['screen', 'systemd']
|
||||
|
||||
if blacklist:
|
||||
if not isinstance(blacklist, list):
|
||||
blacklist = [blacklist]
|
||||
else:
|
||||
blacklist = []
|
||||
|
||||
if excludepid:
|
||||
if not isinstance(excludepid, list):
|
||||
excludepid = [excludepid]
|
||||
else:
|
||||
excludepid = []
|
||||
|
||||
deleted_files = _deleted_files()
|
||||
|
||||
if not isinstance(deleted_files, list):
|
||||
return {'result': False, 'comment': 'Could not get list of processes. '
|
||||
'(Do you have root access?)'}
|
||||
|
||||
owners_cache = {}
|
||||
|
||||
for deleted_file in deleted_files:
|
||||
name, pid, path = deleted_file[0], deleted_file[1], deleted_file[2]
|
||||
if path in blacklist or pid in excludepid:
|
||||
continue
|
||||
readlink = os.readlink('/proc/{0}/exe'.format(pid))
|
||||
try:
|
||||
packagename = owners_cache[readlink]
|
||||
except KeyError:
|
||||
packagename = __salt__['pkg.owner'](readlink)
|
||||
owners_cache[readlink] = packagename
|
||||
if packagename and packagename not in ignorelist:
|
||||
program = '\t' + str(pid) + ' ' + readlink + ' (file: ' + str(path) + ')'
|
||||
if packagename not in packages:
|
||||
packages[packagename] = {'initscripts': [], 'systemdservice': [], 'processes': [program],
|
||||
'process_name': name}
|
||||
else:
|
||||
if program not in packages[packagename]['processes']:
|
||||
packages[packagename]['processes'].append(program)
|
||||
|
||||
if len(packages) == 0 and not kernel_restart:
|
||||
return 'No packages seem to need to be restarted.'
|
||||
|
||||
for package in packages.keys():
|
||||
cmd = cmd_pkg_query + package
|
||||
paths = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
|
||||
|
||||
while True:
|
||||
line = paths.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
pth = line[:-1]
|
||||
if pth.startswith('/etc/init.d/') and not pth.endswith('.sh'):
|
||||
packages[package]['initscripts'].append(pth[12:])
|
||||
|
||||
if os.path.exists(systemd) and pth.startswith(systemd_folder) and pth.endswith('.service') and \
|
||||
pth.find('.wants') == -1:
|
||||
is_oneshot = False
|
||||
servicefile = salt.utils.fopen(pth)
|
||||
sysfold_len = len(systemd_folder)
|
||||
|
||||
for line in servicefile.readlines():
|
||||
if line.find('Type=oneshot') > 0:
|
||||
# scripts that does a single job and then exit
|
||||
is_oneshot = True
|
||||
continue
|
||||
servicefile.close()
|
||||
|
||||
if not is_oneshot:
|
||||
packages[package]['systemdservice'].append(pth[sysfold_len:])
|
||||
|
||||
sys.stdout.flush()
|
||||
paths.stdout.close()
|
||||
|
||||
# Alternatively, find init.d script or service that match the process name
|
||||
for package in packages.keys():
|
||||
if len(packages[package]['systemdservice']) == 0 and len(packages[package]['initscripts']) == 0:
|
||||
service = __salt__['service.available'](packages[package]['process_name'])
|
||||
|
||||
if service:
|
||||
packages[package]['systemdservice'].append(packages[package]['process_name'])
|
||||
else:
|
||||
if os.path.exists('/etc/init.d/' + packages[package]['process_name']):
|
||||
packages[package]['initscripts'].append(packages[package]['process_name'])
|
||||
|
||||
restartable = []
|
||||
nonrestartable = []
|
||||
restartinitcommands = []
|
||||
restartservicecommands = []
|
||||
|
||||
for package in packages.keys():
|
||||
if len(packages[package]['initscripts']) > 0:
|
||||
restartable.append(package)
|
||||
restartinitcommands.extend(['service ' + s + ' restart' for s in packages[package]['initscripts']])
|
||||
elif len(packages[package]['systemdservice']) > 0:
|
||||
restartable.append(package)
|
||||
restartservicecommands.extend(['systemctl restart ' + s for s in packages[package]['systemdservice']])
|
||||
else:
|
||||
nonrestartable.append(package)
|
||||
|
||||
ret = _format_output(kernel_restart, packages, verbose, restartable, nonrestartable,
|
||||
restartservicecommands, restartinitcommands)
|
||||
return ret
|
Loading…
Reference in New Issue
Block a user