From f758314abe0c3c4cc2b06ca5a24c7288a0af41ed Mon Sep 17 00:00:00 2001 From: Guido Günther Date: Fri, 11 Jul 2014 17:23:10 +0200 Subject: Add systemd support --- tests/test_systemd.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++ whatmaps/command.py | 64 ++++++++++++++++++++++++++++++++---------------- whatmaps/debiandistro.py | 7 +++++- whatmaps/systemd.py | 41 +++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 tests/test_systemd.py create mode 100644 whatmaps/systemd.py diff --git a/tests/test_systemd.py b/tests/test_systemd.py new file mode 100644 index 0000000..2fbdfdc --- /dev/null +++ b/tests/test_systemd.py @@ -0,0 +1,62 @@ +# vim: set fileencoding=utf-8 : +# (C) 2014 Guido Günther +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Test L{whatmaps.process} config""" + +import unittest + +from mock import patch + +from whatmaps.systemd import Systemd +from whatmaps.process import Process + +class Process(object): + def __init__(self, pid): + self.pid = pid + +class TestSystemd(unittest.TestCase): + def test_is_init(self): + """Check if we create a systemd object if systemd is the init system in use""" + with patch('os.path.exists', return_value=True): + self.assertIsNotNone(Systemd()) + + def test_is_not_init(self): + """Check if we raise an exception if systemd isn't tthe init system in use""" + with patch('os.path.exists', return_value=False): + self.assertRaises(ValueError, Systemd) + + def test_process_to_unit(self): + p = Process(952) + output = """libvirt-bin.service - Virtualization daemon + Loaded: loaded (/lib/systemd/system/libvirt-bin.service; enabled) + Active: active (running) since Fr 2014-07-11 16:10:55 CEST; 50min ago + Docs: man:libvirtd(8) + http://libvirt.org + Main PID: 952 (libvirtd) + CGroup: name=systemd:/system/libvirt-bin.service + ├─ 952 /usr/sbin/libvirtd + └─1355 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf + """ + with patch('os.path.exists', return_value=True): + with patch('subprocess.Popen') as mock: + PopenMock = mock.return_value + PopenMock.communicate.return_value = [output] + PopenMock.returncode = 0 + result = Systemd().process_to_unit(p) + self.assertEqual(result, 'libvirt-bin.service') + + PopenMock.returncode = 1 + result = Systemd().process_to_unit(p) + self.assertIsNone(result) + diff --git a/whatmaps/command.py b/whatmaps/command.py index 3c22e20..7690ef8 100755 --- a/whatmaps/command.py +++ b/whatmaps/command.py @@ -28,6 +28,7 @@ from optparse import OptionParser from . process import Process from . distro import Distro from . pkg import PkgError +from . systemd import Systemd def check_maps(procs, shared_objects): restart_procs = {} @@ -106,8 +107,25 @@ def find_services(pkgs, distro): return all_services +def find_systemd_units(procmap, distro): + """Find systemd units that contain the given processes""" + units = set() + + for dummy, procs in procmap.items(): + for proc in procs: + unit = Systemd.process_to_unit(proc) + if not unit: + logging.warning("No systemd unit found for '%s'" + "- restart manually" % proc.exe) + else: + units.add(unit) + units -= set([ service + '.service' for service in distro.service_blacklist ]) + return units + + def main(argv): shared_objects = [] + services = None parser = OptionParser(usage='%prog [options] pkg1 [pkg2 pkg3 pkg4]') parser.add_option("--debug", action="store_true", dest="debug", @@ -180,34 +198,38 @@ def main(argv): for exe, pids in restart_procs.items(): logging.debug(" Exe: %s Pids: %s", exe, pids), - # Find the packages that contain the binaries the processes are - # executing - pkgs = find_pkgs(restart_procs, distro) + if Systemd.is_running(): + logging.debug("Detected Systemd") + services = find_systemd_units(restart_procs, distro) + else: + # Find the packages that contain the binaries the processes are + # executing + pkgs = find_pkgs(restart_procs, distro) - # Find the services in these packages honoring distro specific - # mappings and blacklists - try: - all_services = find_services(pkgs, distro) - except NotImplementedError: - if level > logging.INFO: - logging.error("Getting Service listing not implemented " - "for distribution %s - rerun with --verbose to see a list" - "of binaries that map a shared objects from %s", - distro.id, args) - return 1 - else: - return 0 + # Find the services in these packages honoring distro specific + # mappings and blacklists + try: + services = find_services(pkgs, distro) + except NotImplementedError: + if level > logging.INFO: + logging.error("Getting Service listing not implemented " + "for distribution %s - rerun with --verbose to see a list" + "of binaries that map a shared objects from %s", + distro.id, args) + return 1 + else: + return 0 if options.restart: - if options.print_cmds and all_services: - write_cmd_file(all_services, options.print_cmds, distro) + if options.print_cmds and services: + write_cmd_file(services, options.print_cmds, distro) else: - for service in all_services: + for service in services: logging.info("Restarting %s" % service) distro.restart_service(service) - elif all_services: + elif services: print("Services that possibly need to be restarted:") - for s in all_services: + for s in services: print(s) return 0 diff --git a/whatmaps/debiandistro.py b/whatmaps/debiandistro.py index d608d8f..5333a35 100644 --- a/whatmaps/debiandistro.py +++ b/whatmaps/debiandistro.py @@ -34,6 +34,7 @@ import string import distro from . debianpkg import DebianPkg from . pkg import PkgError +from . systemd import Systemd class DebianDistro(distro.Distro): "Debian (dpkg) based distribution" @@ -73,8 +74,12 @@ class DebianDistro(distro.Distro): return DebianPkg(pkg) @classmethod - def restart_service_cmd(klass, name): + def restart_service_cmd(klass, service): """The command that should be used to start a service""" + if Systemd.is_running() and service.endswith('.service'): + name = service[:-len('.service')] + else: + name = service return ['invoke-rc.d', name, 'restart'] @classmethod diff --git a/whatmaps/systemd.py b/whatmaps/systemd.py new file mode 100644 index 0000000..add6d0e --- /dev/null +++ b/whatmaps/systemd.py @@ -0,0 +1,41 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2014 Guido Günther +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import os +import subprocess + + +class Systemd(object): + """Systemd init system""" + + def __init__(self): + if not self.is_running(): + raise ValueError("Systemd not running") + + @staticmethod + def is_running(): + return os.path.exists("/run/systemd/system") + + @staticmethod + def process_to_unit(process): + cmd = ['systemctl', 'status', "%d" % process.pid] + systemctl_status = subprocess.Popen(cmd, stdout=subprocess.PIPE) + output = systemctl_status.communicate()[0] + if systemctl_status.returncode: + return None + else: + return output.split()[0] -- cgit v1.2.3