1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00

lvmdbustest: Add DaemonInfo class

This class handles identifying daemon, sending signals to it, and starting
it back up again.
This commit is contained in:
Tony Asleson 2022-08-23 10:27:30 -05:00
parent ec50979b03
commit de0258a600

View File

@ -9,23 +9,28 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import signal
# noinspection PyUnresolvedReferences
import subprocess
import unittest
from glob import glob
from subprocess import Popen, PIPE
import dbus
import pyudev
# noinspection PyUnresolvedReferences
from dbus.mainloop.glib import DBusGMainLoop
import unittest
import pyudev
from testlib import *
import testlib
from subprocess import Popen, PIPE
from glob import glob
import os
from testlib import *
g_tmo = 0
# Approx. min size
VDO_MIN_SIZE = mib(8192)
EXE_NAME="/lvmdbusd"
# Prefix on created objects to enable easier clean-up
g_prefix = os.getenv('PREFIX', '')
@ -210,6 +215,171 @@ def supports_vdo():
return True
def process_exists(name):
# Walk the process table looking for executable 'name'
for p in [pid for pid in os.listdir('/proc') if pid.isdigit()]:
try:
cmdline_args = read_file_split_nuls("/proc/%s/cmdline" % p)
except OSError:
continue
for arg in cmdline_args:
if name in arg:
return int(p)
return None
def read_file_split_nuls(fn):
with open(fn, "rb") as fh:
return [p.decode("utf-8") for p in fh.read().split(b'\x00') if len(p) > 0]
def read_file_build_hash(fn):
rc = dict()
lines = read_file_split_nuls(fn)
for line in lines:
if line.count("=") == 1:
k, v = line.split("=")
rc[k] = v
return rc
class DaemonInfo(object):
def __init__(self, pid):
# The daemon is running, we have a pid, lets see how it's being run.
# When running under systemd, fd 0 -> /dev/null, fd 1&2 -> socket
# when ran manually it may have output re-directed to a file etc.
# we need the following
# command line arguments
# cwd
# where the output is going (in case it's directed to a file)
# Which lvm binary is being used (check LVM_BINARY env. variable)
# PYTHONPATH
base = "/proc/%d" % pid
self.cwd = os.readlink("%s/cwd" % base)
self.cmdline = read_file_split_nuls("%s/cmdline" % (base))[1:]
self.env = read_file_build_hash("%s/environ" % base)
self.stdin = os.readlink("%s/fd/0" % base)
self.stdout = os.readlink("%s/fd/1" % base)
self.stderr = os.readlink("%s/fd/2" % base)
if self.cwd == "/" and self.stdin == "/dev/null":
self.systemd = True
else:
self.systemd = False
self.process = None
@classmethod
def get(cls):
pid = process_exists(EXE_NAME)
if pid:
return cls(pid)
return None
def start(self, expect_fail=False):
if self.systemd:
pass
else:
stdin_stream = None
stdout_stream = None
stderr_stream = None
try:
stdout_stream = open(self.stdout, "ab")
stdin_stream = open(self.stdin, "rb")
stderr_stream = open(self.stderr, "ab")
self.process = Popen(self.cmdline, cwd=self.cwd, stdin=stdin_stream,
stdout=stdout_stream, stderr=stderr_stream, env=self.env)
if expect_fail:
# Let's wait a bit to see if this process dies as expected and return the exit code
try:
self.process.wait(10)
return self.process.returncode
except subprocess.TimeoutExpired as e:
# Process did not fail as expected, lets kill it
os.kill(self.process.pid, signal.SIGKILL)
self.process.wait(20)
raise e
else:
# This is a hack to set the returncode. When the Popen object goes out of scope during the unit test
# the __del__ method gets called. As we leave the daemon running the process.returncode
# hasn't been set, so it incorrectly raises an exception that the process is still running
# which in our case is correct and expected.
self.process.returncode = 0
finally:
# Close these in the parent
if stdin_stream:
stdin_stream.close()
if stderr_stream:
stderr_stream.close()
if stdout_stream:
stdout_stream.close()
# Make sure daemon is responding to dbus events before returning
DaemonInfo._ensure_daemon("Daemon is not responding on dbus within 20 seconds of starting!")
# During local testing it usually takes ~0.25 seconds for daemon to be ready
return None
@staticmethod
def _ensure_no_daemon():
start = time.time()
pid = process_exists(EXE_NAME)
while pid is not None and (time.time() - start) <= 20:
time.sleep(0.3)
pid = process_exists(EXE_NAME)
if pid:
raise Exception(
"lsmd daemon did not exit within 20 seconds, pid = %s" % pid)
@staticmethod
def _ensure_daemon(msg):
start = time.time()
running = False
while True and (time.time() - start) < 20:
try:
get_objects()
running = True
break
except dbus.exceptions.DBusException:
time.sleep(0.2)
pass
if not running:
raise RuntimeError(msg)
def term_signal(self, sig_number):
# Used for signals that we expect with terminate the daemon, eg. SIGINT, SIGKILL
if self.process:
os.kill(self.process.pid, sig_number)
# Note: The following should work, but doesn't!
# self.process.send_signal(sig_number)
try:
self.process.wait(10)
except subprocess.TimeoutExpired:
std_err_print("Daemon hasn't exited within 10 seconds")
if self.process.poll() is None:
std_err_print("Daemon still running...")
else:
self.process = None
else:
pid = process_exists(EXE_NAME)
os.kill(pid, sig_number)
# Make sure there is no daemon present before we return for things to be "good"
DaemonInfo._ensure_no_daemon()
def non_term_signal(self, sig_number):
if sig_number not in [signal.SIGUSR1, signal.SIGUSR2]:
raise ValueError("Incorrect signal number! %d" % sig_number)
if self.process:
os.kill(self.process.pid, sig_number)
else:
pid = process_exists(EXE_NAME)
os.kill(pid, sig_number)
# noinspection PyUnresolvedReferences
class TestDbusService(unittest.TestCase):
def setUp(self):