]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implements ProcessMonitor in the dhcp_agent
authorMiguel Angel Ajo <mangelajo@redhat.com>
Thu, 21 Aug 2014 10:53:05 +0000 (12:53 +0200)
committerMiguel Angel Ajo <mangelajo@redhat.com>
Fri, 23 Jan 2015 11:55:44 +0000 (11:55 +0000)
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

neutron/agent/dhcp/agent.py
neutron/agent/linux/dhcp.py
neutron/agent/linux/external_process.py
neutron/agent/linux/utils.py
neutron/cmd/netns_cleanup.py
neutron/tests/functional/agent/linux/test_process_monitor.py
neutron/tests/unit/agent/linux/test_process_monitor.py
neutron/tests/unit/test_dhcp_agent.py
neutron/tests/unit/test_linux_dhcp.py
neutron/tests/unit/test_linux_external_process.py
neutron/tests/unit/test_netns_cleanup.py

index 594a78909e1765247f4beb3dcb22beff70781ffe..cf96f4fcf722cd6024e358b2dac290d9dd2e86ec 100644 (file)
@@ -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):
index ec10ba64460c5bf0b13b2debaaa4a5d809b5543e..e41a1de5a6a460c29c83348a4a1741d93d3d02f3 100644 (file)
@@ -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)
 
index 5a57abff6be8a6670e67ead97f0ecf942b2a8f68..87af224518136e6af15231d79e44fb03a624cb16 100644 (file)
@@ -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)
index 45933080605329ae4421414b9f4ad2170fcf4391..2d7aa616f8dccd30305b0ab93c677ecfba73fddb 100644 (file)
@@ -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
index eb18c911eb1d052a356d2c9168c28d5e5b988df5..1f98197cc2922c6736091e5b9ca968dc23e18f63 100644 (file)
@@ -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())
index 1f47d506abde52e57b4e8f986f9c27dd91baab02..8dc6eaa996611b1b341b3aa903cb82844e774ca9 100644 (file)
@@ -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'))
index 3e75687ce63f3e87e5dbd4ea4432d0d6529f8814..28d1d4768b259914275596f621f68b2871e967f9 100644 (file)
@@ -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'))
index 415e9cbb678d6e5b1146cc7bd4fcaf4b630aa21c..367bf929bad6f565c3f0bc7352b1568e121e917f 100644 (file)
@@ -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(
index 9bd6ebe89cefddd14a8c7a1260f717e36fcc8dcc..0b3797d3efd6c9821f6dbb86277053fe8d3b0c68 100644 (file)
@@ -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)])
index b16b681e9309cb1435e203c8d4c0ca2e7ed7610a..1adb06900d65953491797b1d1e2edbd6eb6985ad 100644 (file)
@@ -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'
 
index 9c6ebd889bd262bea1edfd5df7b322ca7afbdd8d..9107ebad2d346f89a62de0533dc20d8c3a99debe 100644 (file)
@@ -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: