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:
parent
ec50979b03
commit
de0258a600
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user