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."""
# 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:
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):
METADATA_PORT = 80
WIN2k3_STATIC_DNS = 249
NS_PREFIX = 'qdhcp-'
+DNSMASQ_SERVICE_NAME = 'dnsmasq'
class DictModel(dict):
@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
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()
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()
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')
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
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',
'--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',
]
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."""
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."""
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)
# under the License.
import collections
+import os.path
import eventlet
from oslo.config import 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__)
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
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
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,
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):
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()
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)
from neutron.common import constants
from neutron.common import utils
+from neutron.i18n import _LE
from neutron.openstack.common import log as logging
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
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):
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
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
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)
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())
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',
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)
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)
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'))
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'))
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],
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,
'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,
)
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)
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)
[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:
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)
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(
# License for the specific language governing permissions and limitations
# under the License.
-import contextlib
import os
import mock
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
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 = []
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")
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):
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'
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
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):
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',
'--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,
['_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 = (
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'])
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:
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)
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'
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'
"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"),
{'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(),
'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)])
'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)])
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'
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: