Check dnsmasq process is active when spawned

After spawning the "dnsmasq" process in the method
"Dnsmasq._spawn_or_reload_process", we need to check that the "dnsmasq"
process is running and could be detected by the ProcessManager instance
controlling it.

ProcessManager determines if a process is "active":
- If the network ID is in the cmdline used to execute the process.
- If the process is detected by psutil.Process(pid), returning the
  cmdline needed in the first condition.
- If the PID file exists; this is written by the dnsmasq process
  once is started and is needed in the second condition.

To make this feature available for any other process using
ProcessManager, the implementation is done in this class.

Change-Id: I51dc9d342c613afcbcfdc50a1d2811502748f170
Closes-Bug: #1849502
(cherry picked from commit 7c5ce50a0c)
(cherry picked from commit ce3f2f7d26)
(cherry picked from commit 2d8613e3c4)
This commit is contained in:
Rodolfo Alonso Hernandez 2019-10-23 14:47:54 +00:00 committed by Slawek Kaplonski
parent 9ab3d21789
commit fc365c8c0d
4 changed files with 26 additions and 4 deletions

View File

@ -470,7 +470,7 @@ class Dnsmasq(DhcpLocalProcess):
pm = self._get_process_manager( pm = self._get_process_manager(
cmd_callback=self._build_cmdline_callback) cmd_callback=self._build_cmdline_callback)
pm.enable(reload_cfg=reload_with_HUP) pm.enable(reload_cfg=reload_with_HUP, ensure_active=True)
self.process_monitor.register(uuid=self.network.id, self.process_monitor.register(uuid=self.network.id,
service_name=DNSMASQ_SERVICE_NAME, service_name=DNSMASQ_SERVICE_NAME,

View File

@ -26,6 +26,7 @@ import six
from neutron.agent.linux import ip_lib from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils from neutron.agent.linux import utils
from neutron.common import utils as common_utils
from neutron.conf.agent import common as agent_cfg from neutron.conf.agent import common as agent_cfg
@ -77,7 +78,7 @@ class ProcessManager(MonitoredProcess):
fileutils.ensure_tree(os.path.dirname(self.get_pid_file_name()), fileutils.ensure_tree(os.path.dirname(self.get_pid_file_name()),
mode=0o755) mode=0o755)
def enable(self, cmd_callback=None, reload_cfg=False): def enable(self, cmd_callback=None, reload_cfg=False, ensure_active=False):
if not self.active: if not self.active:
if not cmd_callback: if not cmd_callback:
cmd_callback = self.default_cmd_callback cmd_callback = self.default_cmd_callback
@ -88,6 +89,8 @@ class ProcessManager(MonitoredProcess):
run_as_root=self.run_as_root) run_as_root=self.run_as_root)
elif reload_cfg: elif reload_cfg:
self.reload_cfg() self.reload_cfg()
if ensure_active:
common_utils.wait_until_true(lambda: self.active)
def reload_cfg(self): def reload_cfg(self):
if self.custom_reload_callback: if self.custom_reload_callback:

View File

@ -1310,7 +1310,7 @@ class TestDnsmasq(TestBase):
self.assertTrue(test_pm.register.called) self.assertTrue(test_pm.register.called)
self.external_process().enable.assert_called_once_with( self.external_process().enable.assert_called_once_with(
reload_cfg=False) ensure_active=True, reload_cfg=False)
call_kwargs = self.external_process.mock_calls[0][2] call_kwargs = self.external_process.mock_calls[0][2]
cmd_callback = call_kwargs['default_cmd_callback'] cmd_callback = call_kwargs['default_cmd_callback']
@ -2052,7 +2052,7 @@ class TestDnsmasq(TestBase):
dm.reload_allocations() dm.reload_allocations()
self.assertTrue(test_pm.register.called) self.assertTrue(test_pm.register.called)
self.external_process().enable.assert_called_once_with( self.external_process().enable.assert_called_once_with(
reload_cfg=True) ensure_active=True, reload_cfg=True)
self.safe.assert_has_calls([ self.safe.assert_has_calls([
mock.call(exp_host_name, exp_host_data), mock.call(exp_host_name, exp_host_data),

View File

@ -19,6 +19,7 @@ from oslo_utils import fileutils
import psutil import psutil
from neutron.agent.linux import external_process as ep from neutron.agent.linux import external_process as ep
from neutron.common import utils as common_utils
from neutron.tests import base from neutron.tests import base
from neutron.tests import tools from neutron.tests import tools
@ -168,6 +169,24 @@ class TestProcessManager(base.BaseTestCase):
manager.enable(callback) manager.enable(callback)
self.assertFalse(callback.called) self.assertFalse(callback.called)
def test_enable_with_ensure_active(self):
def _create_cmd(*args):
return ['sleep', 0]
pm = ep.ProcessManager(self.conf, 'uuid', pid_file='pid_file',
default_cmd_callback=_create_cmd)
with mock.patch.object(psutil, 'Process') as mock_psutil_process, \
mock.patch.object(ep.ProcessManager, 'pid',
new_callable=mock.PropertyMock) as mock_pid:
mock_pid.return_value = 'pid_value'
mock_process = mock.Mock()
mock_process.cmdline.side_effect = [[], ['the', 'cmd', 'uuid']]
mock_psutil_process.return_value = mock_process
try:
pm.enable(ensure_active=True)
except common_utils.WaitTimeout:
self.fail('ProcessManager.enable() raised WaitTimeout')
def test_reload_cfg_without_custom_reload_callback(self): def test_reload_cfg_without_custom_reload_callback(self):
with mock.patch.object(ep.ProcessManager, 'disable') as disable: with mock.patch.object(ep.ProcessManager, 'disable') as disable:
manager = ep.ProcessManager(self.conf, 'uuid', namespace='ns') manager = ep.ProcessManager(self.conf, 'uuid', namespace='ns')