Add process name to AsyncProcess

Since [1], Neutron sets the name of some processes (Neutron agents).
The "ps" output is modified consequently according to the defined
string:
  "<process name> (<process command>)"

"AsyncProcess" class should use the process name to parse the "ps"
output correctly.

Closes-Bug: #1902678

[1]https://review.opendev.org/#/c/735125/

Change-Id: If33c49c0f3e1e6696f5d2aa4008b287dc3f76c61
This commit is contained in:
Rodolfo Alonso Hernandez 2020-11-03 17:05:36 +00:00
parent dc10558c0b
commit ad0605f9c3
4 changed files with 37 additions and 9 deletions

View File

@ -59,7 +59,8 @@ class AsyncProcess(object):
"""
def __init__(self, cmd, run_as_root=False, respawn_interval=None,
namespace=None, log_output=False, die_on_error=False):
namespace=None, log_output=False, die_on_error=False,
process_name=None):
"""Constructor.
:param cmd: The list of command arguments to invoke.
@ -71,6 +72,8 @@ class AsyncProcess(object):
namespace.
:param log_output: Optional, also log received output.
:param die_on_error: Optional, kills the process on stderr output.
:param process_name: Optional, process name set manually by Neutron to
nominate a specific process (e.g.: OVS agent, DHCP agent, etc.).
"""
self.cmd_without_namespace = cmd
self._cmd = ip_lib.add_namespace_to_cmd(cmd, namespace)
@ -86,6 +89,7 @@ class AsyncProcess(object):
self._watchers = []
self.log_output = log_output
self.die_on_error = die_on_error
self.process_name = process_name
@property
def cmd(self):
@ -100,7 +104,8 @@ class AsyncProcess(object):
# spawns rootwrap and rootwrap spawns the process. self.pid will make
# sure to get the correct pid.
return utils.pid_invoked_with_cmdline(
self.pid, self.cmd_without_namespace)
self.pid, self.cmd_without_namespace,
process_name=self.process_name)
def start(self, block=False):
"""Launch a process and monitor it asynchronously.

View File

@ -339,7 +339,14 @@ def get_cmdline_from_pid(pid):
return cmdline
def cmd_matches_expected(cmd, expected_cmd):
def cmd_matches_expected(cmd, expected_cmd, process_name):
if process_name and cmd and cmd[0] == process_name:
# If Neutron has defined the title (setproctitle) of the running
# process, the "ps" output will be "<process_name> (cmd)"
cmd = cmd[1:]
cmd[0] = cmd[0].strip('(')
cmd[-1] = cmd[-1].strip(')')
abs_cmd = remove_abs_path(cmd)
abs_expected_cmd = remove_abs_path(expected_cmd)
if abs_cmd != abs_expected_cmd:
@ -350,12 +357,12 @@ def cmd_matches_expected(cmd, expected_cmd):
return abs_cmd == abs_expected_cmd
def pid_invoked_with_cmdline(pid, expected_cmd):
def pid_invoked_with_cmdline(pid, expected_cmd, process_name=None):
"""Validate process with given pid is running with provided parameters
"""
cmd = get_cmdline_from_pid(pid)
return cmd_matches_expected(cmd, expected_cmd)
return cmd_matches_expected(cmd, expected_cmd, process_name)
def ensure_directory_exists_without_file(path):

View File

@ -69,7 +69,8 @@ class ProcessFixture(fixtures.Fixture):
for filename in self.config_filenames:
cmd += ['--config-file', filename]
self.process = async_process.AsyncProcess(
cmd, run_as_root=run_as_root, namespace=self.namespace
cmd, run_as_root=run_as_root, namespace=self.namespace,
process_name=self.process_name
)
self.process.start(block=True)
LOG.debug("Process started: %s", self.process_name)

View File

@ -402,14 +402,29 @@ class TestPathUtilities(base.BaseTestCase):
def test_cmd_matches_expected_matches_abs_path(self):
cmd = ['/bar/../foo']
self.assertTrue(utils.cmd_matches_expected(cmd, cmd))
self.assertTrue(utils.cmd_matches_expected(cmd, cmd, None))
def test_cmd_matches_expected_matches_script(self):
self.assertTrue(utils.cmd_matches_expected(['python', 'script'],
['script']))
['script'], None))
def test_cmd_matches_expected_doesnt_match(self):
self.assertFalse(utils.cmd_matches_expected('foo', 'bar'))
self.assertFalse(utils.cmd_matches_expected('foo', 'bar', None))
def test_cmd_matches_expected_matches_script_with_procname(self):
self.assertTrue(utils.cmd_matches_expected(
['proc_name', '(python', 'script)'], ['script'], 'proc_name'))
def test_cmd_matches_expected_matches_abs_path_script_with_procname(self):
self.assertTrue(utils.cmd_matches_expected(
['proc_name', '(python', '/bar/../foo)'], ['/bar/../foo'],
'proc_name'))
self.assertTrue(utils.cmd_matches_expected(
['proc_name', '(python', '/bar/../foo', 'input_param)'],
['/bar/../foo', 'input_param'], 'proc_name'))
self.assertTrue(utils.cmd_matches_expected(
['proc_name', '(/bar/../foo', 'input_param)'],
['/bar/../foo', 'input_param'], 'proc_name'))
class FakeUser(object):