From: Miguel Angel Ajo Date: Thu, 21 Aug 2014 10:53:05 +0000 (+0200) Subject: Implements ProcessMonitor in the dhcp_agent X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=fc4e60ed4b1c4ea3b1129ff75364b04f3cb78b91;p=openstack-build%2Fneutron-build.git Implements ProcessMonitor in the dhcp_agent The ProcessMonitor class will watch over spawned external processes, taking the administrator configured action in the case of any of the external processes die unexpectedly. It covers both the neutron-ns-metadata-proxy for isolated metadata and dnsmasq in the dnsmasq driver. ProcessMonitor has been extended to allow specific pid files for backwards-compatible dnsmasq pid file location. Implements: blueprint agent-child-processes-status Change-Id: I0a4a509b65aadf825799c98dfa48a655b123b403 --- diff --git a/neutron/agent/dhcp/agent.py b/neutron/agent/dhcp/agent.py index 594a78909..cf96f4fcf 100644 --- a/neutron/agent/dhcp/agent.py +++ b/neutron/agent/dhcp/agent.py @@ -67,6 +67,24 @@ class DhcpAgent(manager.Manager): os.makedirs(dhcp_dir, 0o755) self.dhcp_version = self.dhcp_driver_cls.check_version() self._populate_networks_cache() + self._process_monitor = external_process.ProcessMonitor( + config=self.conf, + root_helper=self.root_helper, + resource_type='dhcp', + exit_handler=self._exit_handler) + + def _exit_handler(self, uuid, service): + """This is an exit handler for the ProcessMonitor. + + It will be called if the administrator configured the exit action in + check_child_processes_actions, and one of our external processes die + unexpectedly. + """ + LOG.error(_LE("Exiting neutron-dhcp-agent because of a malfunction " + "with the %(service)s process identified by uuid " + "%(uuid)s"), + {'service': service, 'uuid': uuid}) + raise SystemExit(1) def _populate_networks_cache(self): """Populate the networks cache when the DHCP-agent starts.""" @@ -105,10 +123,10 @@ class DhcpAgent(manager.Manager): # the base models. driver = self.dhcp_driver_cls(self.conf, network, + self._process_monitor, self.root_helper, self.dhcp_version, self.plugin_rpc) - getattr(driver, action)(**action_kwargs) return True except exceptions.Conflict: @@ -368,20 +386,13 @@ class DhcpAgent(manager.Manager): cfg.CONF, 'neutron-ns-metadata-proxy-%s.log' % network.id)) return proxy_cmd - pm = external_process.ProcessManager( - self.conf, - network.id, - self.root_helper, - network.namespace) - pm.enable(callback) + self._process_monitor.enable(uuid=network.id, + cmd_callback=callback, + namespace=network.namespace) def disable_isolated_metadata_proxy(self, network): - pm = external_process.ProcessManager( - self.conf, - network.id, - self.root_helper, - network.namespace) - pm.disable() + self._process_monitor.disable(uuid=network.id, + namespace=network.namespace) class DhcpPluginApi(object): diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index ec10ba644..e41a1de5a 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -51,6 +51,7 @@ METADATA_DEFAULT_CIDR = '%s/%d' % (METADATA_DEFAULT_IP, METADATA_PORT = 80 WIN2k3_STATIC_DNS = 249 NS_PREFIX = 'qdhcp-' +DNSMASQ_SERVICE_NAME = 'dnsmasq' class DictModel(dict): @@ -111,11 +112,12 @@ class NetModel(DictModel): @six.add_metaclass(abc.ABCMeta) class DhcpBase(object): - def __init__(self, conf, network, root_helper='sudo', + def __init__(self, conf, network, process_monitor, root_helper='sudo', version=None, plugin=None): self.conf = conf self.network = network self.root_helper = root_helper + self.process_monitor = process_monitor self.device_manager = DeviceManager(self.conf, self.root_helper, plugin) self.version = version @@ -167,10 +169,10 @@ class DhcpBase(object): class DhcpLocalProcess(DhcpBase): PORTS = [] - def __init__(self, conf, network, root_helper='sudo', + def __init__(self, conf, network, process_monitor, root_helper='sudo', version=None, plugin=None): - super(DhcpLocalProcess, self).__init__(conf, network, root_helper, - version, plugin) + super(DhcpLocalProcess, self).__init__(conf, network, process_monitor, + root_helper, version, plugin) self.confs_dir = self.get_confs_dir(conf) self.network_conf_dir = os.path.join(self.confs_dir, network.id) self._ensure_network_conf_dir() @@ -210,21 +212,18 @@ class DhcpLocalProcess(DhcpBase): def disable(self, retain_port=False): """Disable DHCP for this network by killing the local process.""" - pid = self.pid + pid_filename = self.get_conf_file_name('pid') - if pid: - if self.active: - cmd = ['kill', '-9', pid] - utils.execute(cmd, self.root_helper) - else: - LOG.debug('DHCP for %(net_id)s is stale, pid %(pid)d ' - 'does not exist, performing cleanup', - {'net_id': self.network.id, 'pid': pid}) - if not retain_port: - self.device_manager.destroy(self.network, - self.interface_name) - else: - LOG.debug('No DHCP started for %s', self.network.id) + pid = self.process_monitor.get_pid(uuid=self.network.id, + service=DNSMASQ_SERVICE_NAME, + pid_file=pid_filename) + + self.process_monitor.disable(uuid=self.network.id, + namespace=self.network.namespace, + service=DNSMASQ_SERVICE_NAME, + pid_file=pid_filename) + if pid and not retain_port: + self.device_manager.destroy(self.network, self.interface_name) self._remove_config_files() @@ -255,24 +254,6 @@ class DhcpLocalProcess(DhcpBase): LOG.debug(msg, file_name) return None - @property - def pid(self): - """Last known pid for the DHCP process spawned for this network.""" - return self._get_value_from_conf_file('pid', int) - - @property - def active(self): - pid = self.pid - if pid is None: - return False - - cmdline = '/proc/%s/cmdline' % pid - try: - with open(cmdline, "r") as f: - return self.network.id in f.readline() - except IOError: - return False - @property def interface_name(self): return self._get_value_from_conf_file('interface') @@ -282,6 +263,13 @@ class DhcpLocalProcess(DhcpBase): interface_file_path = self.get_conf_file_name('interface') utils.replace_file(interface_file_path, value) + @property + def active(self): + pid_filename = self.get_conf_file_name('pid') + return self.process_monitor.is_active(self.network.id, + DNSMASQ_SERVICE_NAME, + pid_file=pid_filename) + @abc.abstractmethod def spawn_process(self): pass @@ -333,12 +321,7 @@ class Dnsmasq(DhcpLocalProcess): except OSError: return [] - def spawn_process(self): - """Spawns a Dnsmasq process for the network.""" - env = { - self.NEUTRON_NETWORK_ID_KEY: self.network.id, - } - + def _build_cmdline_callback(self, pid_file): cmd = [ 'dnsmasq', '--no-hosts', @@ -347,10 +330,10 @@ class Dnsmasq(DhcpLocalProcess): '--bind-interfaces', '--interface=%s' % self.interface_name, '--except-interface=lo', - '--pid-file=%s' % self.get_conf_file_name('pid'), - '--dhcp-hostsfile=%s' % self._output_hosts_file(), - '--addn-hosts=%s' % self._output_addn_hosts_file(), - '--dhcp-optsfile=%s' % self._output_opts_file(), + '--pid-file=%s' % pid_file, + '--dhcp-hostsfile=%s' % self.get_conf_file_name('host'), + '--addn-hosts=%s' % self.get_conf_file_name('addn_hosts'), + '--dhcp-optsfile=%s' % self.get_conf_file_name('opts'), '--leasefile-ro', ] @@ -409,9 +392,31 @@ class Dnsmasq(DhcpLocalProcess): if self.conf.dhcp_broadcast_reply: cmd.append('--dhcp-broadcast') - ip_wrapper = ip_lib.IPWrapper(self.root_helper, - self.network.namespace) - ip_wrapper.netns.execute(cmd, addl_env=env) + return cmd + + def spawn_process(self): + """Spawn the process, if it's not spawned already.""" + self._spawn_or_reload_process(reload_with_HUP=False) + + def _spawn_or_reload_process(self, reload_with_HUP): + """Spawns or reloads a Dnsmasq process for the network. + + When reload_with_HUP is True, dnsmasq receives a HUP signal, + or it's reloaded if the process is not running. + """ + + self._output_config_files() + + pid_filename = self.get_conf_file_name('pid') + + self.process_monitor.enable( + uuid=self.network.id, + cmd_callback=self._build_cmdline_callback, + namespace=self.network.namespace, + service=DNSMASQ_SERVICE_NAME, + cmd_addl_env={self.NEUTRON_NETWORK_ID_KEY: self.network.id}, + reload_cfg=reload_with_HUP, + pid_file=pid_filename) def _release_lease(self, mac_address, ip): """Release a DHCP lease.""" @@ -420,6 +425,11 @@ class Dnsmasq(DhcpLocalProcess): self.network.namespace) ip_wrapper.netns.execute(cmd) + def _output_config_files(self): + self._output_hosts_file() + self._output_addn_hosts_file() + self._output_opts_file() + def reload_allocations(self): """Rebuild the dnsmasq config and signal the dnsmasq to reload.""" @@ -431,14 +441,7 @@ class Dnsmasq(DhcpLocalProcess): return self._release_unused_leases() - self._output_hosts_file() - self._output_addn_hosts_file() - self._output_opts_file() - if self.active: - cmd = ['kill', '-HUP', self.pid] - utils.execute(cmd, self.root_helper) - else: - LOG.debug('Pid %d is stale, relaunching dnsmasq', self.pid) + self._spawn_or_reload_process(reload_with_HUP=True) LOG.debug('Reloading allocations for network: %s', self.network.id) self.device_manager.update(self.network, self.interface_name) diff --git a/neutron/agent/linux/external_process.py b/neutron/agent/linux/external_process.py index 5a57abff6..87af22451 100644 --- a/neutron/agent/linux/external_process.py +++ b/neutron/agent/linux/external_process.py @@ -13,6 +13,7 @@ # under the License. import collections +import os.path import eventlet from oslo.config import cfg @@ -22,6 +23,7 @@ from neutron.agent.common import config as agent_cfg from neutron.agent.linux import ip_lib from neutron.agent.linux import utils from neutron.i18n import _LE +from neutron.openstack.common import fileutils from neutron.openstack.common import log as logging LOG = logging.getLogger(__name__) @@ -46,7 +48,7 @@ class ProcessManager(object): def __init__(self, conf, uuid, root_helper='sudo', namespace=None, service=None, pids_path=None, default_cmd_callback=None, - cmd_addl_env=None): + cmd_addl_env=None, pid_file=None): self.conf = conf self.uuid = uuid @@ -55,6 +57,7 @@ class ProcessManager(object): self.default_cmd_callback = default_cmd_callback self.cmd_addl_env = cmd_addl_env self.pids_path = pids_path or self.conf.external_pids + self.pid_file = pid_file if service: self.service_pid_fname = 'pid.' + service @@ -85,9 +88,7 @@ class ProcessManager(object): utils.execute(cmd, self.root_helper) # In the case of shutting down, remove the pid file if sig == '9': - utils.remove_conf_file(self.pids_path, - self.uuid, - self.service_pid_fname) + fileutils.delete_if_exists(self.get_pid_file_name()) elif pid: LOG.debug('Process for %(uuid)s pid %(pid)d is stale, ignoring ' 'signal %(signal)s', {'uuid': self.uuid, 'pid': pid, @@ -97,18 +98,20 @@ class ProcessManager(object): def get_pid_file_name(self, ensure_pids_dir=False): """Returns the file name for a given kind of config file.""" - return utils.get_conf_file_name(self.pids_path, - self.uuid, - self.service_pid_fname, - ensure_pids_dir) + if self.pid_file: + if ensure_pids_dir: + utils.ensure_dir(os.path.dirname(self.pid_file)) + return self.pid_file + else: + return utils.get_conf_file_name(self.pids_path, + self.uuid, + self.service_pid_fname, + ensure_pids_dir) @property def pid(self): """Last known pid for this external process spawned for this uuid.""" - return utils.get_value_from_conf_file(self.pids_path, - self.uuid, - self.service_pid_fname, - int) + return utils.get_value_from_file(self.get_pid_file_name(), int) @property def active(self): @@ -154,38 +157,53 @@ class ProcessMonitor(object): self._spawn_checking_thread() def enable(self, uuid, cmd_callback, namespace=None, service=None, - reload_cfg=False, cmd_addl_env=None): - """Creates a process and ensures that it is monitored. - - It will create a new ProcessManager and tie it to the uuid/service. + reload_cfg=False, cmd_addl_env=None, pid_file=None): + """Creates a process manager and ensures that it is monitored. + + It will create a new ProcessManager and tie it to the uuid/service + with the new settings, replacing the old one if it existed already. + + :param uuid: UUID of the resource this process will serve for. + :param cmd_callback: Callback function that receives a pid_file + location and returns a list with the command + and arguments. + :param namespace: network namespace to run the process in, if + necessary. + :param service: a logical name for the service this process provides, + it will extend the pid file like pid.%(service)s. + :param reload_cfg: if the process is active send a HUP signal + for configuration reload, otherwise spawn it + normally. + :param cmd_addl_env: additional environment variables for the + spawned process. + :param pid_file: the pid file to store the pid of the external + process. If not provided, a default will be used. """ - process_manager = ProcessManager(conf=self._config, - uuid=uuid, - root_helper=self._root_helper, - namespace=namespace, - service=service, - default_cmd_callback=cmd_callback, - cmd_addl_env=cmd_addl_env) + process_manager = self._create_process_manager( + uuid=uuid, + cmd_callback=cmd_callback, + namespace=namespace, + service=service, + cmd_addl_env=cmd_addl_env, + pid_file=pid_file) process_manager.enable(reload_cfg=reload_cfg) service_id = ServiceId(uuid, service) + + # replace the old process manager with the new one self._process_managers[service_id] = process_manager - def disable(self, uuid, namespace=None, service=None): + def disable(self, uuid, namespace=None, service=None, + pid_file=None): """Disables the process and stops monitoring it.""" service_id = ServiceId(uuid, service) - process_manager = self._process_managers.pop(service_id, None) - # we could be trying to disable a process_manager which was - # started on a separate run of this agent, or during netns-cleanup - # therefore we won't know about such uuid and we need to - # build the process_manager to kill it - if not process_manager: - process_manager = ProcessManager(conf=self._config, - uuid=uuid, - root_helper=self._root_helper, - namespace=namespace, - service=service) + process_manager = self._ensure_process_manager( + uuid=uuid, + service=service, + pid_file=pid_file, + namespace=namespace) + self._process_managers.pop(service_id, None) process_manager.disable() @@ -198,18 +216,47 @@ class ProcessMonitor(object): service_id = ServiceId(uuid, service) return self._process_managers.get(service_id) - def _get_process_manager_attribute(self, attribute, uuid, service=None): - process_manager = self.get_process_manager(uuid, service) - if process_manager: - return getattr(process_manager, attribute) - else: - return False + def is_active(self, uuid, service=None, pid_file=None): + return self._ensure_process_manager( + uuid=uuid, + service=service, + pid_file=pid_file).active + + def get_pid(self, uuid, service=None, pid_file=None): + return self._ensure_process_manager( + uuid=uuid, + service=service, + pid_file=pid_file).pid - def is_active(self, uuid, service=None): - return self._get_process_manager_attribute('active', uuid, service) + def _ensure_process_manager(self, uuid, cmd_callback=None, + namespace=None, service=None, + cmd_addl_env=None, + pid_file=None, + ): - def get_pid(self, uuid, service=None): - return self._get_process_manager_attribute('pid', uuid, service) + process_manager = self.get_process_manager(uuid, service) + if not process_manager: + # if the process existed in a different run of the agent + # provide one, generally for pid / active evaluation + process_manager = self._create_process_manager( + uuid=uuid, + cmd_callback=cmd_callback, + namespace=namespace, + service=service, + cmd_addl_env=cmd_addl_env, + pid_file=pid_file) + return process_manager + + def _create_process_manager(self, uuid, cmd_callback, namespace, service, + cmd_addl_env, pid_file): + return ProcessManager(conf=self._config, + uuid=uuid, + root_helper=self._root_helper, + namespace=namespace, + service=service, + default_cmd_callback=cmd_callback, + cmd_addl_env=cmd_addl_env, + pid_file=pid_file) def _spawn_checking_thread(self): eventlet.spawn(self._periodic_checking_thread) diff --git a/neutron/agent/linux/utils.py b/neutron/agent/linux/utils.py index 459330806..2d7aa616f 100644 --- a/neutron/agent/linux/utils.py +++ b/neutron/agent/linux/utils.py @@ -27,6 +27,7 @@ from oslo.utils import excutils from neutron.common import constants from neutron.common import utils +from neutron.i18n import _LE from neutron.openstack.common import log as logging @@ -133,12 +134,17 @@ def find_child_pids(pid): return [x.strip() for x in raw_pids.split('\n') if x.strip()] +def ensure_dir(dir_path): + """Ensure a directory with 755 permissions mode.""" + if not os.path.isdir(dir_path): + os.makedirs(dir_path, 0o755) + + def _get_conf_base(cfg_root, uuid, ensure_conf_dir): conf_dir = os.path.abspath(os.path.normpath(cfg_root)) conf_base = os.path.join(conf_dir, uuid) if ensure_conf_dir: - if not os.path.isdir(conf_dir): - os.makedirs(conf_dir, 0o755) + ensure_dir(conf_dir) return conf_base @@ -148,22 +154,22 @@ def get_conf_file_name(cfg_root, uuid, cfg_file, ensure_conf_dir=False): return "%s.%s" % (conf_base, cfg_file) -def get_value_from_conf_file(cfg_root, uuid, cfg_file, converter=None): - """A helper function to read a value from one of a config file.""" - file_name = get_conf_file_name(cfg_root, uuid, cfg_file) - msg = _('Error while reading %s') +def get_value_from_file(filename, converter=None): try: - with open(file_name, 'r') as f: + with open(filename, 'r') as f: try: return converter(f.read()) if converter else f.read() except ValueError: - msg = _('Unable to convert value in %s') + LOG.error(_LE('Unable to convert value in %s'), filename) except IOError: - msg = _('Unable to access %s') + LOG.debug('Unable to access %s', filename) - LOG.debug(msg, file_name) - return None + +def get_value_from_conf_file(cfg_root, uuid, cfg_file, converter=None): + """A helper function to read a value from one of a config file.""" + file_name = get_conf_file_name(cfg_root, uuid, cfg_file) + return get_value_from_file(file_name, converter) def remove_conf_files(cfg_root, uuid): @@ -172,13 +178,6 @@ def remove_conf_files(cfg_root, uuid): os.unlink(file_path) -def remove_conf_file(cfg_root, uuid, cfg_file): - """Remove a config file.""" - conf_file = get_conf_file_name(cfg_root, uuid, cfg_file) - if os.path.exists(conf_file): - os.unlink(conf_file) - - def get_root_helper_child_pid(pid, root_helper=None): """ Get the lowest child pid in the process hierarchy diff --git a/neutron/cmd/netns_cleanup.py b/neutron/cmd/netns_cleanup.py index eb18c911e..1f98197cc 100644 --- a/neutron/cmd/netns_cleanup.py +++ b/neutron/cmd/netns_cleanup.py @@ -25,6 +25,7 @@ from neutron.agent.common import config as agent_config from neutron.agent.dhcp import config as dhcp_config from neutron.agent.l3 import agent as l3_agent from neutron.agent.linux import dhcp +from neutron.agent.linux import external_process from neutron.agent.linux import interface from neutron.agent.linux import ip_lib from neutron.agent.linux import ovs_lib @@ -72,6 +73,14 @@ def setup_conf(): return conf +def _get_dhcp_process_monitor(config, root_helper): + return external_process.ProcessMonitor( + config=config, + root_helper=root_helper, + resource_type='dhcp', + exit_handler=lambda: None) + + def kill_dhcp(conf, namespace): """Disable DHCP for a network if DHCP is still active.""" root_helper = agent_config.get_root_helper(conf) @@ -80,6 +89,7 @@ def kill_dhcp(conf, namespace): dhcp_driver = importutils.import_object( conf.dhcp_driver, conf=conf, + process_monitor=_get_dhcp_process_monitor(conf, root_helper), network=dhcp.NetModel(conf.use_namespaces, {'id': network_id}), root_helper=root_helper, plugin=FakeDhcpPlugin()) diff --git a/neutron/tests/functional/agent/linux/test_process_monitor.py b/neutron/tests/functional/agent/linux/test_process_monitor.py index 1f47d506a..8dc6eaa99 100644 --- a/neutron/tests/functional/agent/linux/test_process_monitor.py +++ b/neutron/tests/functional/agent/linux/test_process_monitor.py @@ -32,11 +32,15 @@ class BaseTestProcessMonitor(base.BaseSudoTestCase): cfg.CONF.set_override('check_child_processes_interval', 1, 'AGENT') self._child_processes = [] self._ext_processes = None + self.create_child_processes_manager('respawn') self.addCleanup(self.cleanup_spawned_children) def create_child_processes_manager(self, action): cfg.CONF.set_override('check_child_processes_action', action, 'AGENT') - self._ext_processes = external_process.ProcessMonitor( + self._ext_processes = self.build_process_monitor() + + def build_process_monitor(self): + return external_process.ProcessMonitor( config=cfg.CONF, root_helper=None, resource_type='test', @@ -54,7 +58,7 @@ class BaseTestProcessMonitor(base.BaseSudoTestCase): return cmdline return _cmdline_callback - def _spawn_n_children(self, n, service=None): + def spawn_n_children(self, n, service=None): self._child_processes = [] for child_number in moves.xrange(n): uuid = self._child_uuid(child_number) @@ -74,7 +78,7 @@ class BaseTestProcessMonitor(base.BaseSudoTestCase): self._child_processes[-1].disable() def spawn_child_processes_and_kill_last(self, service=None, number=2): - self._spawn_n_children(number, service) + self.spawn_n_children(number, service) self._kill_last_child() self.assertFalse(self._child_processes[-1].active) @@ -102,6 +106,17 @@ class BaseTestProcessMonitor(base.BaseSudoTestCase): class TestProcessMonitor(BaseTestProcessMonitor): def test_respawn_handler(self): - self.create_child_processes_manager('respawn') self.spawn_child_processes_and_kill_last() self.wait_for_all_childs_respawned() + + def test_new_process_monitor_finds_old_process(self): + self.spawn_n_children(1) + spawn_process = self._child_processes[-1] + uuid = spawn_process.uuid + + another_pm = self.build_process_monitor() + self.assertTrue(another_pm.is_active(uuid)) + self.assertEqual(another_pm.get_pid(uuid), spawn_process.pid) + + def test_tries_to_get_pid_for_unknown_uuid(self): + self.assertIsNone(self._ext_processes.get_pid('bad-uuid')) diff --git a/neutron/tests/unit/agent/linux/test_process_monitor.py b/neutron/tests/unit/agent/linux/test_process_monitor.py index 3e75687ce..28d1d4768 100644 --- a/neutron/tests/unit/agent/linux/test_process_monitor.py +++ b/neutron/tests/unit/agent/linux/test_process_monitor.py @@ -94,6 +94,3 @@ class TestProcessMonitor(BaseTestProcessMonitor): def test_pid_method_with_service(self): self.test_pid_method(TEST_PID) - - def test_pid_method_unknown_uuid(self): - self.assertFalse(self.pmonitor.get_pid('bad-uuid')) diff --git a/neutron/tests/unit/test_dhcp_agent.py b/neutron/tests/unit/test_dhcp_agent.py index 415e9cbb6..367bf929b 100644 --- a/neutron/tests/unit/test_dhcp_agent.py +++ b/neutron/tests/unit/test_dhcp_agent.py @@ -117,7 +117,10 @@ fake_dist_port = dhcp.DictModel(dict(id='12345678-1234-aaaa-1234567890ab', device_id='forzanapoli', fixed_ips=[fake_meta_fixed_ip])) -fake_network = dhcp.NetModel(True, dict(id='12345678-1234-5678-1234567890ab', +FAKE_NETWORK_UUID = '12345678-1234-5678-1234567890ab' +FAKE_NETWORK_DHCP_NS = "qdhcp-%s" % FAKE_NETWORK_UUID + +fake_network = dhcp.NetModel(True, dict(id=FAKE_NETWORK_UUID, tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa', admin_state_up=True, subnets=[fake_subnet1, fake_subnet2], @@ -262,6 +265,7 @@ class TestDhcpAgent(base.BaseTestCase): dhcp = dhcp_agent.DhcpAgent(cfg.CONF) self.assertTrue(dhcp.call_driver('foo', network)) self.driver.assert_called_once_with(cfg.CONF, + mock.ANY, mock.ANY, 'sudo', mock.ANY, @@ -278,6 +282,7 @@ class TestDhcpAgent(base.BaseTestCase): 'schedule_resync') as schedule_resync: self.assertIsNone(dhcp.call_driver('foo', network)) self.driver.assert_called_once_with(cfg.CONF, + mock.ANY, mock.ANY, 'sudo', mock.ANY, @@ -548,24 +553,30 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): ) self.external_process = self.external_process_p.start() + def _process_manager_constructor_call(self): + return mock.call(conf=cfg.CONF, + uuid=FAKE_NETWORK_UUID, + root_helper='sudo', + namespace=FAKE_NETWORK_DHCP_NS, + service=None, + default_cmd_callback=mock.ANY, + pid_file=None, + cmd_addl_env=None) + def _enable_dhcp_helper(self, network, enable_isolated_metadata=False, is_isolated_network=False): if enable_isolated_metadata: cfg.CONF.set_override('enable_isolated_metadata', True) self.plugin.get_network_info.return_value = network self.dhcp.enable_dhcp_helper(network.id) - self.plugin.assert_has_calls( - [mock.call.get_network_info(network.id)]) + self.plugin.assert_has_calls([ + mock.call.get_network_info(network.id)]) self.call_driver.assert_called_once_with('enable', network) self.cache.assert_has_calls([mock.call.put(network)]) if is_isolated_network: self.external_process.assert_has_calls([ - mock.call( - cfg.CONF, - '12345678-1234-5678-1234567890ab', - 'sudo', - 'qdhcp-12345678-1234-5678-1234567890ab'), - mock.call().enable(mock.ANY) + self._process_manager_constructor_call(), + mock.call().enable(reload_cfg=False) ]) else: self.assertFalse(self.external_process.call_count) @@ -687,13 +698,8 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): self.call_driver.assert_called_once_with('disable', fake_network) if isolated_metadata: self.external_process.assert_has_calls([ - mock.call( - cfg.CONF, - '12345678-1234-5678-1234567890ab', - 'sudo', - 'qdhcp-12345678-1234-5678-1234567890ab'), - mock.call().disable() - ]) + self._process_manager_constructor_call(), + mock.call().disable()]) else: self.assertFalse(self.external_process.call_count) @@ -724,11 +730,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): [mock.call.get_network_by_id(fake_network.id)]) if isolated_metadata: self.external_process.assert_has_calls([ - mock.call( - cfg.CONF, - '12345678-1234-5678-1234567890ab', - 'sudo', - 'qdhcp-12345678-1234-5678-1234567890ab'), + self._process_manager_constructor_call(), mock.call().disable() ]) else: @@ -741,54 +743,41 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): self._disable_dhcp_helper_driver_failure() def test_enable_isolated_metadata_proxy(self): - class_path = 'neutron.agent.linux.external_process.ProcessManager' - with mock.patch(class_path) as ext_process: - self.dhcp.enable_isolated_metadata_proxy(fake_network) - ext_process.assert_has_calls([ - mock.call( - cfg.CONF, - '12345678-1234-5678-1234567890ab', - 'sudo', - 'qdhcp-12345678-1234-5678-1234567890ab'), - mock.call().enable(mock.ANY) - ]) + self.dhcp.enable_isolated_metadata_proxy(fake_network) + self.external_process.assert_has_calls([ + self._process_manager_constructor_call(), + mock.call().enable(reload_cfg=False) + ]) def test_disable_isolated_metadata_proxy(self): - class_path = 'neutron.agent.linux.external_process.ProcessManager' - with mock.patch(class_path) as ext_process: - self.dhcp.disable_isolated_metadata_proxy(fake_network) - ext_process.assert_has_calls([ - mock.call( - cfg.CONF, - '12345678-1234-5678-1234567890ab', - 'sudo', - 'qdhcp-12345678-1234-5678-1234567890ab'), - mock.call().disable() - ]) + self.dhcp.disable_isolated_metadata_proxy(fake_network) + self.external_process.assert_has_calls([ + self._process_manager_constructor_call(), + mock.call().disable() + ]) def _test_metadata_network(self, network): cfg.CONF.set_override('enable_metadata_network', True) cfg.CONF.set_override('debug', True) cfg.CONF.set_override('verbose', False) cfg.CONF.set_override('log_file', 'test.log') - class_path = 'neutron.agent.linux.ip_lib.IPWrapper' - self.external_process_p.stop() - with mock.patch(class_path) as ip_wrapper: - self.dhcp.enable_isolated_metadata_proxy(network) - ip_wrapper.assert_has_calls([mock.call( - 'sudo', - 'qdhcp-12345678-1234-5678-1234567890ab'), - mock.call().netns.execute([ - 'neutron-ns-metadata-proxy', - mock.ANY, - mock.ANY, - '--router_id=forzanapoli', - mock.ANY, - mock.ANY, - '--debug', - ('--log-file=neutron-ns-metadata-proxy-%s.log' % - network.id)], addl_env=None) - ]) + self.dhcp.enable_isolated_metadata_proxy(network) + + self.external_process.assert_has_calls([ + self._process_manager_constructor_call()]) + + callback = self.external_process.call_args[1]['default_cmd_callback'] + result_cmd = callback('pidfile') + self.assertEqual( + result_cmd, + ['neutron-ns-metadata-proxy', + '--pid_file=pidfile', + '--metadata_proxy_socket=%s' % cfg.CONF.metadata_proxy_socket, + '--router_id=forzanapoli', + '--state_path=%s' % cfg.CONF.state_path, + '--metadata_port=%d' % dhcp.METADATA_PORT, + '--debug', + '--log-file=neutron-ns-metadata-proxy-%s.log' % network.id]) def test_enable_isolated_metadata_proxy_with_metadata_network(self): self._test_metadata_network(fake_meta_network) @@ -1297,7 +1286,7 @@ class TestDeviceManager(base.BaseTestCase): def test_destroy(self): fake_net = dhcp.NetModel( - True, dict(id='12345678-1234-5678-1234567890ab', + True, dict(id=FAKE_NETWORK_UUID, tenant_id='aaaaaaaa-aaaa-aaaa-aaaaaaaaaaaa')) fake_port = dhcp.DictModel( diff --git a/neutron/tests/unit/test_linux_dhcp.py b/neutron/tests/unit/test_linux_dhcp.py index 9bd6ebe89..0b3797d3e 100644 --- a/neutron/tests/unit/test_linux_dhcp.py +++ b/neutron/tests/unit/test_linux_dhcp.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import contextlib import os import mock @@ -24,6 +23,7 @@ import testtools from neutron.agent.common import config from neutron.agent.dhcp import config as dhcp_config from neutron.agent.linux import dhcp +from neutron.agent.linux import external_process from neutron.common import config as base_config from neutron.common import constants from neutron.openstack.common import log as logging @@ -488,6 +488,8 @@ class LocalChild(dhcp.DhcpLocalProcess): PORTS = {4: [4], 6: [6]} def __init__(self, *args, **kwargs): + self.process_monitor = mock.Mock() + kwargs['process_monitor'] = self.process_monitor super(LocalChild, self).__init__(*args, **kwargs) self.called = [] @@ -508,6 +510,7 @@ class TestBase(base.BaseTestCase): self.conf.register_opts(base_config.core_opts) self.conf.register_opts(dhcp_config.DHCP_OPTS) self.conf.register_opts(dhcp_config.DNSMASQ_OPTS) + self.conf.register_opts(external_process.OPTS) config.register_interface_driver_opts_helper(self.conf) config.register_use_namespaces_opts_helper(self.conf) instance = mock.patch("neutron.agent.linux.dhcp.DeviceManager") @@ -546,7 +549,8 @@ class TestDhcpBase(TestBase): def test_restart(self): class SubClass(dhcp.DhcpBase): def __init__(self): - dhcp.DhcpBase.__init__(self, cfg.CONF, FakeV4Network(), None) + dhcp.DhcpBase.__init__(self, cfg.CONF, FakeV4Network(), + mock.Mock(), None) self.called = [] def enable(self): @@ -568,41 +572,6 @@ class TestDhcpBase(TestBase): class TestDhcpLocalProcess(TestBase): - def test_active(self): - with mock.patch('__builtin__.open') as mock_open: - mock_open.return_value.__enter__ = lambda s: s - mock_open.return_value.__exit__ = mock.Mock() - mock_open.return_value.readline.return_value = \ - 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' - - with mock.patch.object(LocalChild, 'pid') as pid: - pid.__get__ = mock.Mock(return_value=4) - lp = LocalChild(self.conf, FakeV4Network()) - self.assertTrue(lp.active) - - mock_open.assert_called_once_with('/proc/4/cmdline', 'r') - - def test_active_none(self): - dummy_cmd_line = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' - self.execute.return_value = (dummy_cmd_line, '') - with mock.patch.object(LocalChild, 'pid') as pid: - pid.__get__ = mock.Mock(return_value=None) - lp = LocalChild(self.conf, FakeV4Network()) - self.assertFalse(lp.active) - - def test_active_cmd_mismatch(self): - with mock.patch('__builtin__.open') as mock_open: - mock_open.return_value.__enter__ = lambda s: s - mock_open.return_value.__exit__ = mock.Mock() - mock_open.return_value.readline.return_value = \ - 'bbbbbbbb-bbbb-bbbb-aaaa-aaaaaaaaaaaa' - - with mock.patch.object(LocalChild, 'pid') as pid: - pid.__get__ = mock.Mock(return_value=4) - lp = LocalChild(self.conf, FakeV4Network()) - self.assertFalse(lp.active) - - mock_open.assert_called_once_with('/proc/4/cmdline', 'r') def test_get_conf_file_name(self): tpl = '/dhcp/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/dev' @@ -652,102 +621,59 @@ class TestDhcpLocalProcess(TestBase): def test_disable_not_active(self): attrs_to_mock = dict([(a, mock.DEFAULT) for a in - ['active', 'interface_name', 'pid']]) + ['active', 'interface_name']]) with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks: mocks['active'].__get__ = mock.Mock(return_value=False) - mocks['pid'].__get__ = mock.Mock(return_value=5) mocks['interface_name'].__get__ = mock.Mock(return_value='tap0') - with mock.patch.object(dhcp.LOG, 'debug') as log: - network = FakeDualNetwork() - lp = LocalChild(self.conf, network) - lp.device_manager = mock.Mock() - lp.disable() - msg = log.call_args[0][0] - self.assertIn('does not exist', msg) - lp.device_manager.destroy.assert_called_once_with( - network, 'tap0') - - def test_disable_unknown_network(self): - attrs_to_mock = dict([(a, mock.DEFAULT) for a in - ['active', 'interface_name', 'pid']]) - with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks: - mocks['active'].__get__ = mock.Mock(return_value=False) - mocks['pid'].__get__ = mock.Mock(return_value=None) - mocks['interface_name'].__get__ = mock.Mock(return_value='tap0') - with mock.patch.object(dhcp.LOG, 'debug') as log: - lp = LocalChild(self.conf, FakeDualNetwork()) - lp.disable() - msg = log.call_args[0][0] - self.assertIn('No DHCP', msg) + network = FakeDualNetwork() + lp = LocalChild(self.conf, network) + lp.process_monitor.pid.return_value = 5 + lp.device_manager = mock.Mock() + lp.disable() + lp.device_manager.destroy.assert_called_once_with( + network, 'tap0') def test_disable_retain_port(self): attrs_to_mock = dict([(a, mock.DEFAULT) for a in - ['active', 'interface_name', 'pid']]) + ['active', 'interface_name']]) network = FakeDualNetwork() with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks: mocks['active'].__get__ = mock.Mock(return_value=True) - mocks['pid'].__get__ = mock.Mock(return_value=5) mocks['interface_name'].__get__ = mock.Mock(return_value='tap0') lp = LocalChild(self.conf, network) lp.disable(retain_port=True) - - exp_args = ['kill', '-9', 5] - self.execute.assert_called_once_with(exp_args, 'sudo') + self.assertTrue(lp.process_monitor.disable.called) def test_disable(self): attrs_to_mock = dict([(a, mock.DEFAULT) for a in - ['active', 'interface_name', 'pid']]) + ['active', 'interface_name']]) network = FakeDualNetwork() with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks: mocks['active'].__get__ = mock.Mock(return_value=True) - mocks['pid'].__get__ = mock.Mock(return_value=5) mocks['interface_name'].__get__ = mock.Mock(return_value='tap0') lp = LocalChild(self.conf, network) with mock.patch('neutron.agent.linux.ip_lib.IPWrapper') as ip: + lp.process_monitor.pid.return_value = 5 lp.disable() self.mock_mgr.assert_has_calls([mock.call(self.conf, 'sudo', None), mock.call().destroy(network, 'tap0')]) - exp_args = ['kill', '-9', 5] - self.execute.assert_called_once_with(exp_args, 'sudo') self.assertEqual(ip.return_value.netns.delete.call_count, 0) def test_disable_delete_ns(self): self.conf.set_override('dhcp_delete_namespaces', True) - attrs_to_mock = dict([(a, mock.DEFAULT) for a in ['active', 'pid']]) + attrs_to_mock = {'active': mock.DEFAULT} with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks: mocks['active'].__get__ = mock.Mock(return_value=False) - mocks['pid'].__get__ = mock.Mock(return_value=False) lp = LocalChild(self.conf, FakeDualNetwork()) with mock.patch('neutron.agent.linux.ip_lib.IPWrapper') as ip: + lp.process_monitor.pid.return_value = 5 lp.disable() ip.return_value.netns.delete.assert_called_with('qdhcp-ns') - def test_pid(self): - with mock.patch('__builtin__.open') as mock_open: - mock_open.return_value.__enter__ = lambda s: s - mock_open.return_value.__exit__ = mock.Mock() - mock_open.return_value.read.return_value = '5' - lp = LocalChild(self.conf, FakeDualNetwork()) - self.assertEqual(lp.pid, 5) - - def test_pid_no_an_int(self): - with mock.patch('__builtin__.open') as mock_open: - mock_open.return_value.__enter__ = lambda s: s - mock_open.return_value.__exit__ = mock.Mock() - mock_open.return_value.read.return_value = 'foo' - lp = LocalChild(self.conf, FakeDualNetwork()) - self.assertIsNone(lp.pid) - - def test_pid_invalid_file(self): - with mock.patch.object(LocalChild, 'get_conf_file_name') as conf_file: - conf_file.return_value = '.doesnotexist/pid' - lp = LocalChild(self.conf, FakeDualNetwork()) - self.assertIsNone(lp.pid) - def test_get_interface_name(self): with mock.patch('__builtin__.open') as mock_open: mock_open.return_value.__enter__ = lambda s: s @@ -767,6 +693,13 @@ class TestDhcpLocalProcess(TestBase): class TestDnsmasq(TestBase): + + def _get_dnsmasq(self, network, process_monitor=None): + process_monitor = process_monitor or mock.Mock() + return dhcp.Dnsmasq(self.conf, network, + version=dhcp.Dnsmasq.MINIMUM_VERSION, + process_monitor=process_monitor) + def _test_spawn(self, extra_options, network=FakeDualNetwork(), max_leases=16777216, lease_duration=86400, has_static=True): @@ -779,13 +712,13 @@ class TestDnsmasq(TestBase): else: raise IndexError() + # if you need to change this path here, think twice, + # that means pid files will move around, breaking upgrades + # or backwards-compatibility + expected_pid_file = '/dhcp/%s/pid' % network.id + + expected_env = {'NEUTRON_NETWORK_ID': network.id} expected = [ - 'ip', - 'netns', - 'exec', - 'qdhcp-ns', - 'env', - 'NEUTRON_NETWORK_ID=%s' % network.id, 'dnsmasq', '--no-hosts', '--no-resolv', @@ -793,7 +726,7 @@ class TestDnsmasq(TestBase): '--bind-interfaces', '--interface=tap0', '--except-interface=lo', - '--pid-file=/dhcp/%s/pid' % network.id, + '--pid-file=%s' % expected_pid_file, '--dhcp-hostsfile=/dhcp/%s/host' % network.id, '--addn-hosts=/dhcp/%s/addn_hosts' % network.id, '--dhcp-optsfile=/dhcp/%s/opts' % network.id, @@ -834,6 +767,8 @@ class TestDnsmasq(TestBase): ['_output_opts_file', 'get_conf_file_name', 'interface_name']] ) + test_pm = mock.Mock() + with mock.patch.multiple(dhcp.Dnsmasq, **attrs_to_mock) as mocks: mocks['get_conf_file_name'].side_effect = mock_get_conf_file_name mocks['_output_opts_file'].return_value = ( @@ -843,14 +778,24 @@ class TestDnsmasq(TestBase): with mock.patch.object(dhcp.sys, 'argv') as argv: argv.__getitem__.side_effect = fake_argv - dm = dhcp.Dnsmasq(self.conf, network, - version=dhcp.Dnsmasq.MINIMUM_VERSION) + dm = self._get_dnsmasq(network, test_pm) dm.spawn_process() self.assertTrue(mocks['_output_opts_file'].called) - self.execute.assert_called_once_with(expected, - root_helper='sudo', - check_exit_code=True, - extra_ok_codes=None) + + test_pm.enable.assert_called_once_with( + cmd_addl_env=expected_env, + uuid=network.id, + service='dnsmasq', + namespace='qdhcp-ns', + cmd_callback=mock.ANY, + reload_cfg=False, + pid_file=expected_pid_file) + call_kwargs = test_pm.method_calls[0][2] + cmd_callback = call_kwargs['cmd_callback'] + + result_cmd = cmd_callback(expected_pid_file) + + self.assertEqual(expected, result_cmd) def test_spawn(self): self._test_spawn(['--conf-file=', '--domain=openstacklocal']) @@ -907,8 +852,7 @@ class TestDnsmasq(TestBase): def _test_output_opts_file(self, expected, network, ipm_retval=None): with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn: conf_fn.return_value = '/foo/opts' - dm = dhcp.Dnsmasq(self.conf, network, - version=dhcp.Dnsmasq.MINIMUM_VERSION) + dm = self._get_dnsmasq(network) if ipm_retval: with mock.patch.object( dm, '_make_subnet_interface_ip_map') as ipm: @@ -1089,8 +1033,7 @@ class TestDnsmasq(TestBase): with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn: conf_fn.return_value = '/foo/opts' - dm = dhcp.Dnsmasq(self.conf, FakeV4NetworkMultipleTags(), - version=dhcp.Dnsmasq.MINIMUM_VERSION) + dm = self._get_dnsmasq(FakeV4NetworkMultipleTags()) dm._output_opts_file() self.safe.assert_called_once_with('/foo/opts', expected) @@ -1176,63 +1119,30 @@ class TestDnsmasq(TestBase): exp_addn_name, exp_addn_data, exp_opt_name, exp_opt_data,) = self._test_reload_allocation_data - exp_args = ['kill', '-HUP', 5] - - fake_net = FakeDualNetwork() - dm = dhcp.Dnsmasq(self.conf, fake_net, - version=dhcp.Dnsmasq.MINIMUM_VERSION) - - with contextlib.nested( - mock.patch.object(dhcp.Dnsmasq, 'active'), - mock.patch.object(dhcp.Dnsmasq, 'pid'), - mock.patch.object(dhcp.Dnsmasq, 'interface_name'), - mock.patch.object(dhcp.Dnsmasq, '_make_subnet_interface_ip_map'), - mock.patch.object(dm, 'device_manager') - ) as (active, pid, interface_name, ip_map, device_manager): - active.__get__ = mock.Mock(return_value=True) - pid.__get__ = mock.Mock(return_value=5) - interface_name.__get__ = mock.Mock(return_value='tap12345678-12') - ip_map.return_value = {} - dm.reload_allocations() - - self.assertTrue(ip_map.called) - self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data), - mock.call(exp_addn_name, exp_addn_data), - mock.call(exp_opt_name, exp_opt_data)]) - self.execute.assert_called_once_with(exp_args, 'sudo') - device_manager.update.assert_called_with(fake_net, 'tap12345678-12') - - def test_reload_allocations_stale_pid(self): - (exp_host_name, exp_host_data, - exp_addn_name, exp_addn_data, - exp_opt_name, exp_opt_data,) = self._test_reload_allocation_data - with mock.patch('__builtin__.open') as mock_open: mock_open.return_value.__enter__ = lambda s: s mock_open.return_value.__exit__ = mock.Mock() mock_open.return_value.readline.return_value = None - with mock.patch.object(dhcp.Dnsmasq, 'pid') as pid: - pid.__get__ = mock.Mock(return_value=5) - dm = dhcp.Dnsmasq(self.conf, FakeDualNetwork(), - version=dhcp.Dnsmasq.MINIMUM_VERSION) - - method_name = '_make_subnet_interface_ip_map' - with mock.patch.object(dhcp.Dnsmasq, method_name) as ipmap: - ipmap.return_value = {} - with mock.patch.object(dhcp.Dnsmasq, 'interface_name'): - dm.reload_allocations() - self.assertTrue(ipmap.called) + test_pm = mock.Mock() + dm = self._get_dnsmasq(FakeDualNetwork(), test_pm) + dm.reload_allocations() + test_pm.enable.assert_has_calls([mock.call(uuid=mock.ANY, + cmd_callback=mock.ANY, + namespace=mock.ANY, + service=mock.ANY, + cmd_addl_env=mock.ANY, + reload_cfg=True, + pid_file=mock.ANY)]) self.safe.assert_has_calls([ mock.call(exp_host_name, exp_host_data), mock.call(exp_addn_name, exp_addn_data), mock.call(exp_opt_name, exp_opt_data), ]) - mock_open.assert_called_once_with('/proc/5/cmdline', 'r') def test_release_unused_leases(self): - dnsmasq = dhcp.Dnsmasq(self.conf, FakeDualNetwork()) + dnsmasq = self._get_dnsmasq(FakeDualNetwork()) ip1 = '192.168.1.2' mac1 = '00:00:80:aa:bb:cc' @@ -1252,7 +1162,7 @@ class TestDnsmasq(TestBase): any_order=True) def test_release_unused_leases_one_lease(self): - dnsmasq = dhcp.Dnsmasq(self.conf, FakeDualNetwork()) + dnsmasq = self._get_dnsmasq(FakeDualNetwork()) ip1 = '192.168.0.2' mac1 = '00:00:80:aa:bb:cc' @@ -1281,7 +1191,7 @@ class TestDnsmasq(TestBase): "00:00:80:aa:bb:cc,inst-name,[fdca:3ba5:a17a::1]"] mock_open.return_value.readlines.return_value = lines - dnsmasq = dhcp.Dnsmasq(self.conf, FakeDualNetwork()) + dnsmasq = self._get_dnsmasq(FakeDualNetwork()) leases = dnsmasq._read_hosts_file_leases(filename) self.assertEqual(set([("192.168.0.1", "00:00:80:aa:bb:cc"), @@ -1296,8 +1206,7 @@ class TestDnsmasq(TestBase): {'cidr': '192.168.0.1/24'} ] - dm = dhcp.Dnsmasq(self.conf, - FakeDualNetwork()) + dm = self._get_dnsmasq(FakeDualNetwork()) self.assertEqual( dm._make_subnet_interface_ip_map(), @@ -1378,8 +1287,7 @@ class TestDnsmasq(TestBase): '192.168.0.4\n' '00:00:0f:rr:rr:rr,host-192-168-0-1.openstacklocal,' '192.168.0.1\n').lstrip() - dm = dhcp.Dnsmasq(self.conf, FakeDualStackNetworkSingleDHCP(), - version=dhcp.Dnsmasq.MINIMUM_VERSION) + dm = self._get_dnsmasq(FakeDualStackNetworkSingleDHCP()) dm._output_hosts_file() self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data)]) @@ -1394,8 +1302,7 @@ class TestDnsmasq(TestBase): '192.168.0.4\n' '00:00:0f:rr:rr:rr,host-192-168-0-1.openstacklocal,' '192.168.0.1\n').lstrip() - dm = dhcp.Dnsmasq(self.conf, FakeDualNetworkSingleDHCP(), - version=dhcp.Dnsmasq.MINIMUM_VERSION) + dm = self._get_dnsmasq(FakeDualNetworkSingleDHCP()) dm._output_hosts_file() self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data)]) diff --git a/neutron/tests/unit/test_linux_external_process.py b/neutron/tests/unit/test_linux_external_process.py index b16b681e9..1adb06900 100644 --- a/neutron/tests/unit/test_linux_external_process.py +++ b/neutron/tests/unit/test_linux_external_process.py @@ -23,6 +23,9 @@ class TestProcessManager(base.BaseTestCase): super(TestProcessManager, self).setUp() self.execute_p = mock.patch('neutron.agent.linux.utils.execute') self.execute = self.execute_p.start() + self.delete_if_exists = mock.patch( + 'neutron.openstack.common.fileutils.delete_if_exists').start() + self.conf = mock.Mock() self.conf.external_pids = '/var/path' diff --git a/neutron/tests/unit/test_netns_cleanup.py b/neutron/tests/unit/test_netns_cleanup.py index 9c6ebd889..9107ebad2 100644 --- a/neutron/tests/unit/test_netns_cleanup.py +++ b/neutron/tests/unit/test_netns_cleanup.py @@ -47,7 +47,8 @@ class TestNetnsCleanup(base.BaseTestCase): expected_params = {'conf': conf, 'network': mock.ANY, 'root_helper': conf.AGENT.root_helper, - 'plugin': mock.ANY} + 'plugin': mock.ANY, + 'process_monitor': mock.ANY} import_object.assert_called_once_with('driver', **expected_params) if dhcp_active: