diff --git a/neutron/agent/linux/utils.py b/neutron/agent/linux/utils.py index e186874a720..73ad326bfe2 100644 --- a/neutron/agent/linux/utils.py +++ b/neutron/agent/linux/utils.py @@ -164,20 +164,14 @@ def find_child_pids(pid, recursive=False): It can also find all children through the hierarchy if recursive=True """ try: - raw_pids = execute(['ps', '--ppid', pid, '-o', 'pid='], - log_fail_as_error=False) - except exceptions.ProcessExecutionError as e: - # Unexpected errors are the responsibility of the caller - with excutils.save_and_reraise_exception() as ctxt: - # Exception has already been logged by execute - no_children_found = e.returncode == 1 - if no_children_found: - ctxt.reraise = False - return [] - child_pids = [x.strip() for x in raw_pids.split('\n') if x.strip()] + process = psutil.Process(pid=int(pid)) + except psutil.NoSuchProcess: + return [] + + child_pids = [str(p.pid) for p in process.children()] if recursive: for child in child_pids: - child_pids = child_pids + find_child_pids(child, True) + child_pids += find_child_pids(child, recursive=True) return child_pids diff --git a/neutron/tests/functional/agent/linux/test_utils.py b/neutron/tests/functional/agent/linux/test_utils.py index dde8f225090..b65233efd58 100644 --- a/neutron/tests/functional/agent/linux/test_utils.py +++ b/neutron/tests/functional/agent/linux/test_utils.py @@ -141,3 +141,34 @@ class TestGetProcessCountByName(functional_base.BaseSudoTestCase): # NOTE(ralonsoh): other tests can spawn sleep processes too, but at # this point we know there are, at least, 20 "sleep" processes running. self.assertLessEqual(20, number_of_sleep) + + +class TestFindChildPids(functional_base.BaseSudoTestCase): + + def _stop_process(self, process): + process.stop(kill_signal=signal.SIGKILL) + + def test_find_child_pids(self): + pid = os.getppid() + child_pids = utils.find_child_pids(pid) + child_pids_recursive = utils.find_child_pids(pid, recursive=True) + for _pid in child_pids: + self.assertIn(_pid, child_pids_recursive) + + cmd = ['sleep', '100'] + process = async_process.AsyncProcess(cmd) + process.start() + common_utils.wait_until_true(lambda: process._process.pid, + sleep=0.5, timeout=10) + self.addCleanup(self._stop_process, process) + + child_pids_after = utils.find_child_pids(pid) + child_pids_recursive_after = utils.find_child_pids(pid, recursive=True) + self.assertEqual(child_pids, child_pids_after) + for _pid in child_pids + [process.pid]: + self.assertIn(_pid, child_pids_recursive_after) + + def test_find_non_existing_process(self): + with open('/proc/sys/kernel/pid_max', 'r') as fd: + pid_max = int(fd.readline().strip()) + self.assertEqual([], utils.find_child_pids(pid_max)) diff --git a/neutron/tests/unit/agent/linux/test_utils.py b/neutron/tests/unit/agent/linux/test_utils.py index 8160be95941..308c7f0adad 100644 --- a/neutron/tests/unit/agent/linux/test_utils.py +++ b/neutron/tests/unit/agent/linux/test_utils.py @@ -313,37 +313,6 @@ class TestGetCmdlineFromPid(base.BaseTestCase): self.assertEqual([], cmdline) -class TestFindChildPids(base.BaseTestCase): - - def test_returns_empty_list_for_exit_code_1(self): - with mock.patch.object(utils, 'execute', - side_effect=exceptions.ProcessExecutionError( - '', returncode=1)): - self.assertEqual([], utils.find_child_pids(-1)) - - def test_returns_empty_list_for_no_output(self): - with mock.patch.object(utils, 'execute', return_value=''): - self.assertEqual([], utils.find_child_pids(-1)) - - def test_returns_list_of_child_process_ids_for_good_ouput(self): - with mock.patch.object(utils, 'execute', return_value=' 123 \n 185\n'): - self.assertEqual(utils.find_child_pids(-1), ['123', '185']) - - def test_returns_list_of_child_process_ids_recursively(self): - with mock.patch.object(utils, 'execute', - side_effect=[' 123 \n 185\n', - ' 40 \n', '\n', - '41\n', '\n']): - actual = utils.find_child_pids(-1, True) - self.assertEqual(actual, ['123', '185', '40', '41']) - - def test_raises_unknown_exception(self): - with testtools.ExpectedException(RuntimeError): - with mock.patch.object(utils, 'execute', - side_effect=RuntimeError()): - utils.find_child_pids(-1) - - class TestGetRoothelperChildPid(base.BaseTestCase): def _test_get_root_helper_child_pid(self, expected=_marker, run_as_root=False, pids=None,