Skip to content

Commit

Permalink
Fixes #36973 - Use dnf needs-restarting to collect tracer information (
Browse files Browse the repository at this point in the history
…#149)

* Fixes #36973 - Use dnf needs-restarting to collect tracer information

Co-Authored-by: Ewoud Kohl van Wijngaarden <ewoud@kohlvanwijngaarden.nl>

* Update src/katello/tracer/dnf.py

Co-authored-by: Ian Ballou <ianballou67@gmail.com>

---------

Co-authored-by: Ewoud Kohl van Wijngaarden <ewoud@kohlvanwijngaarden.nl>
Co-authored-by: Ian Ballou <ianballou67@gmail.com>
  • Loading branch information
3 people authored Apr 2, 2024
1 parent f494bb9 commit 1408cbd
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 15 deletions.
49 changes: 49 additions & 0 deletions src/katello/tracer/SystemdDbus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# SystemdUnit.py
# Module for getting data from Systemd about Units

# Copyright (C) 2017 Sean O'Keeffe
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#

import dbus

class SystemdDbus(object):
def __init__(self):
self.__systemd = dbus.SystemBus().get_object('org.freedesktop.systemd1','/org/freedesktop/systemd1')
self.__manager = dbus.Interface(self.__systemd, dbus_interface='org.freedesktop.systemd1.Manager')

def unit_path_from_pid(self, pid):
try:
return self.__manager.GetUnitByPID(pid)
except dbus.exceptions.DBusException:
return False

def has_service_property_from_pid(self, pid, attr):
try:
unit = self.unit_path_from_pid(pid)
if not unit:
return False

proxy = dbus.SystemBus().get_object('org.freedesktop.systemd1', unit)
propty = proxy.Get('org.freedesktop.systemd1.Service', attr, dbus_interface='org.freedesktop.DBus.Properties')
except dbus.exceptions.DBusException:
return False
return bool(propty)

def get_unit_property_from_pid(self, pid, attr):
unit_path = self.unit_path_from_pid(pid)
if bool(unit_path):
proxy = dbus.SystemBus().get_object('org.freedesktop.systemd1', self.unit_path_from_pid(pid))
return proxy.Get('org.freedesktop.systemd1.Unit', attr, dbus_interface='org.freedesktop.DBus.Properties')
else:
return False
29 changes: 14 additions & 15 deletions src/katello/tracer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,34 @@
def collect_apps():
raise NotImplementedError("Couldn't detect package manager. Failed to query affected apps!")

try:
imp.find_module('apt')
from katello.tracer.deb import collect_apps
except ImportError:
pass

# RHEL based systems
try:
imp.find_module('dnf')
tracer = True
from katello.tracer.dnf import collect_apps
except ImportError:
try:
imp.find_module('yum')
tracer = True
from tracer.query import Query
def collect_apps():
query = Query()
return query.affected_applications().get()
except ImportError:
tracer = False
pass

if tracer:
from tracer.query import Query
def collect_apps():
query = Query()
return query.affected_applications().get()
# debian based systems
try:
imp.find_module('apt')
from katello.tracer.deb import collect_apps
except ImportError:
pass

# SUSE based systems
try:
imp.find_module('zypp_plugin')
from katello.tracer.zypper import collect_apps
except ImportError:
pass


def upload_tracer_profile(queryfunc, plugin=None):
uep = get_uep()
consumer_id = lookup_consumer_id()
Expand Down
118 changes: 118 additions & 0 deletions src/katello/tracer/dnf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import os
import re
import subprocess
import psutil
from katello.tracer.SystemdDbus import SystemdDbus

# these services need a reboot of the system
STATIC_SERVICES = [
"systemd",
"dbus",
]

# these apps are ignored and no tracer will be added
IGNORE_APPS = [
"sudo",
"su",
"(sd-pam)",
]

REBOOT_HELPER= 'You will have to reboot your computer'
SESSION_HELPER = 'You will have to log out & log in again'

class DnfTracerApp:
def __init__(self, name, helper, app_type):
self.name = name
self.helper = helper
self.type = app_type


class Process:
def __init__(self, bus, pid):
self.bus = bus
self.pid = int(pid)
self.process = psutil.Process(self.pid)
self.name = self.detect_name()

# special handling for ssh sessions. Thanks to
# https://github.com/FrostyX/tracer/blob/ff8fc924fcbe2f638dd88b50549813dab2b8595b/tracer/resources/processes.py#L79
try:
if self.name == 'sshd':
exe = self.process.exe()
cmdline = self.process.cmdline()
if exe not in cmdline and len(cmdline) > 1:
self.name = 'ssh-{0}-session'.format(re.split(' |@',' '.join(cmdline))[1])
except psutil.AccessDenied:
pass

def detect_name(self):
if self.pid and self.bus.unit_path_from_pid(self.pid):
if not self.bus.has_service_property_from_pid(self.pid, 'PAMName'):
unit_id = self.bus.get_unit_property_from_pid(self.pid, 'Id')
if unit_id and unit_id.endswith('.service'):
return unit_id[:-8]
return self.process.name()

def is_session(self):
if self.name.startswith("ssh-") and self.name.endswith("-session"):
return True

terminal = self.process.terminal()
if terminal is not None:
parent = self.process.parent()
if parent is None or terminal != parent.terminal():
return True
return False

def is_reboot_required(self):
return self.name in STATIC_SERVICES


def collect_services_state():
env = dict(os.environ, LANG='C')
process = subprocess.run(['dnf', 'needs-restarting'], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
if process.returncode != 0:
return []

lines = process.stdout.split('\n')
pids = [line.split(' : ')[0].strip() for line in lines if ' : ' in line]

apps = set()
bus = SystemdDbus()
added_services = []

for pid in pids:
p = Process(bus, pid)

if p.name in IGNORE_APPS:
continue

app_type = 'daemon'
helper = "systemctl restart " + p.name
if p.is_session():
app_type = 'session'
helper = SESSION_HELPER
if p.is_reboot_required():
app_type = 'static'
helper = REBOOT_HELPER

if p.name is not None and p.name not in added_services:
app = DnfTracerApp(p.name, helper, app_type)
apps.add(app)
added_services.append(p.name)
return list(apps)


def collect_restart():
apps = []
process = subprocess.run(["dnf", "needs-restarting", "-r"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if process.returncode == 1:
app = DnfTracerApp("kernel", REBOOT_HELPER, "static")
apps.append(app)
return apps


def collect_apps(plugin=None):
apps = collect_services_state()
reboot = collect_restart()
return apps + reboot

0 comments on commit 1408cbd

Please sign in to comment.