Merge "Add kill hooks for external processes"
This commit is contained in:
commit
c3e611eaf1
@ -43,3 +43,36 @@ vary between hosts in a neutron deployment such as the ``local_ip`` for an L2
|
|||||||
agent. If any agent requires access to additional external services beyond the
|
agent. If any agent requires access to additional external services beyond the
|
||||||
neutron RPC, those endpoints should be defined in the agent-specific
|
neutron RPC, those endpoints should be defined in the agent-specific
|
||||||
configuration file (for example, nova metadata for metadata agent).
|
configuration file (for example, nova metadata for metadata agent).
|
||||||
|
|
||||||
|
External processes run by agents
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Some neutron agents, like DHCP, Metadata or L3, often run external
|
||||||
|
processes to provide some of their functionalities. It may be keepalived,
|
||||||
|
dnsmasq, haproxy or some other process.
|
||||||
|
Neutron agents are responsible for spawning and killing such processes when
|
||||||
|
necessary. By default, to kill such processes, agents use a simple ``kill``
|
||||||
|
command, but in some cases, like for example when those additional services
|
||||||
|
are running inside containers, it may be not a good solution.
|
||||||
|
To address this problem, operators should use the ``AGENT`` config group option
|
||||||
|
``kill_scripts_path`` to configure a path to where ``kill scripts`` for such
|
||||||
|
processes live. By default, it is set to ``/etc/neutron/kill_scripts/``.
|
||||||
|
If option ``kill_scripts_path`` is changed in the config to the different
|
||||||
|
location, ``exec_dirs`` in ``/etc/rootwrap.conf`` should be changed accordingly.
|
||||||
|
If ``kill_scripts_path`` is set, every time neutron has to kill a process,
|
||||||
|
for example ``dnsmasq``, it will look in this directory for a file with the name
|
||||||
|
``<process_name>-kill``. So for ``dnsmasq`` process it will look for a
|
||||||
|
``dnsmasq-kill`` script. If such a file exists there, it will be called
|
||||||
|
instead of using the ``kill`` command.
|
||||||
|
|
||||||
|
Kill scripts are called with two parameters:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
<process>-kill <sig> <pid>
|
||||||
|
|
||||||
|
where: ``<sig>`` is the signal, same as with the ``kill`` command, for example
|
||||||
|
``9`` or ``SIGKILL``; and ``<pid>`` is pid of the process to kill.
|
||||||
|
|
||||||
|
This external script should then handle killing of the given process as neutron
|
||||||
|
will not call the ``kill`` command for it anymore.
|
||||||
|
@ -15,6 +15,8 @@ dnsmasq: CommandFilter, dnsmasq, root
|
|||||||
# neutron/agent/linux/dhcp.py
|
# neutron/agent/linux/dhcp.py
|
||||||
kill_dnsmasq: KillFilter, root, /sbin/dnsmasq, -9, -HUP, -15
|
kill_dnsmasq: KillFilter, root, /sbin/dnsmasq, -9, -HUP, -15
|
||||||
kill_dnsmasq_usr: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP, -15
|
kill_dnsmasq_usr: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP, -15
|
||||||
|
# dnsmasq kill script filter
|
||||||
|
kill_dnsmasq_script: CommandFilter, dnsmasq-kill, root
|
||||||
|
|
||||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||||
mm-ctl: CommandFilter, mm-ctl, root
|
mm-ctl: CommandFilter, mm-ctl, root
|
||||||
|
@ -15,3 +15,7 @@
|
|||||||
# prefix_delegation_agent
|
# prefix_delegation_agent
|
||||||
dibbler-client: CommandFilter, dibbler-client, root
|
dibbler-client: CommandFilter, dibbler-client, root
|
||||||
kill_dibbler-client: KillFilter, root, dibbler-client, -9
|
kill_dibbler-client: KillFilter, root, dibbler-client, -9
|
||||||
|
# dibbler kill script filter
|
||||||
|
kill_dibbler_script: CommandFilter, dibbler-kill, root
|
||||||
|
# dibbler-client kill script filter
|
||||||
|
kill_dibbler-client_script: CommandFilter, dibbler-client-kill, root
|
||||||
|
@ -19,6 +19,8 @@ radvd: CommandFilter, radvd, root
|
|||||||
# haproxy
|
# haproxy
|
||||||
haproxy: RegExpFilter, haproxy, root, haproxy, -f, .*
|
haproxy: RegExpFilter, haproxy, root, haproxy, -f, .*
|
||||||
kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP
|
kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP
|
||||||
|
# haproxy kill script filter
|
||||||
|
kill_haproxy_script: CommandFilter, haproxy-kill, root
|
||||||
|
|
||||||
kill_radvd_usr: KillFilter, root, /usr/sbin/radvd, -15, -9, -HUP
|
kill_radvd_usr: KillFilter, root, /usr/sbin/radvd, -15, -9, -HUP
|
||||||
kill_radvd: KillFilter, root, /sbin/radvd, -15, -9, -HUP
|
kill_radvd: KillFilter, root, /sbin/radvd, -15, -9, -HUP
|
||||||
@ -52,6 +54,8 @@ ip6tables-restore: CommandFilter, ip6tables-restore, root
|
|||||||
# Keepalived
|
# Keepalived
|
||||||
keepalived: CommandFilter, keepalived, root
|
keepalived: CommandFilter, keepalived, root
|
||||||
kill_keepalived: KillFilter, root, keepalived, -HUP, -15, -9
|
kill_keepalived: KillFilter, root, keepalived, -HUP, -15, -9
|
||||||
|
# keepalived kill script filter
|
||||||
|
kill_keepalived_script: CommandFilter, keepalived-kill, root
|
||||||
|
|
||||||
# l3 agent to delete floatingip's conntrack state
|
# l3 agent to delete floatingip's conntrack state
|
||||||
conntrack: CommandFilter, conntrack, root
|
conntrack: CommandFilter, conntrack, root
|
||||||
@ -75,3 +79,5 @@ kill_keepalived_monitor_py37: KillFilter, root, python3.7, -15
|
|||||||
# absolute path
|
# absolute path
|
||||||
kill_keepalived_monitor_platform_py: KillFilter, root, /usr/libexec/platform-python, -15
|
kill_keepalived_monitor_platform_py: KillFilter, root, /usr/libexec/platform-python, -15
|
||||||
kill_keepalived_monitor_platform_py36: KillFilter, root, /usr/libexec/platform-python3.6, -15
|
kill_keepalived_monitor_platform_py36: KillFilter, root, /usr/libexec/platform-python3.6, -15
|
||||||
|
# neutron-keepalived-state-change-monitor kill script filter
|
||||||
|
kill_neutron-keepalived-state-change-monitor_script: CommandFilter, neutron-keepalived-state-change-monitor-kill, root
|
||||||
|
@ -10,7 +10,7 @@ filters_path=/etc/neutron/rootwrap.d,/usr/share/neutron/rootwrap
|
|||||||
# explicitely specify a full path (separated by ',')
|
# explicitely specify a full path (separated by ',')
|
||||||
# If not specified, defaults to system PATH environment variable.
|
# If not specified, defaults to system PATH environment variable.
|
||||||
# These directories MUST all be only writeable by root !
|
# These directories MUST all be only writeable by root !
|
||||||
exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin
|
exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin,/etc/neutron/kill_scripts
|
||||||
|
|
||||||
# Enable logging to syslog
|
# Enable logging to syslog
|
||||||
# Default value is False
|
# Default value is False
|
||||||
|
@ -35,6 +35,8 @@ LOG = logging.getLogger(__name__)
|
|||||||
HA_DEV_PREFIX = 'ha-'
|
HA_DEV_PREFIX = 'ha-'
|
||||||
IP_MONITOR_PROCESS_SERVICE = 'ip_monitor'
|
IP_MONITOR_PROCESS_SERVICE = 'ip_monitor'
|
||||||
SIGTERM_TIMEOUT = 10
|
SIGTERM_TIMEOUT = 10
|
||||||
|
KEEPALIVED_STATE_CHANGE_MONITOR_SERVICE_NAME = (
|
||||||
|
"neutron-keepalived-state-change-monitor")
|
||||||
|
|
||||||
# TODO(liuyulong): move to neutron-lib?
|
# TODO(liuyulong): move to neutron-lib?
|
||||||
STATE_CHANGE_PROC_NAME = 'neutron-keepalived-state-change'
|
STATE_CHANGE_PROC_NAME = 'neutron-keepalived-state-change'
|
||||||
@ -360,6 +362,7 @@ class HaRouter(router.RouterInfo):
|
|||||||
self.agent_conf,
|
self.agent_conf,
|
||||||
'%s.monitor' % self.router_id,
|
'%s.monitor' % self.router_id,
|
||||||
self.ha_namespace,
|
self.ha_namespace,
|
||||||
|
service=KEEPALIVED_STATE_CHANGE_MONITOR_SERVICE_NAME,
|
||||||
default_cmd_callback=self._get_state_change_monitor_callback())
|
default_cmd_callback=self._get_state_change_monitor_callback())
|
||||||
|
|
||||||
def _get_state_change_monitor_callback(self):
|
def _get_state_change_monitor_callback(self):
|
||||||
|
@ -242,6 +242,7 @@ class DhcpLocalProcess(DhcpBase):
|
|||||||
conf=self.conf,
|
conf=self.conf,
|
||||||
uuid=self.network.id,
|
uuid=self.network.id,
|
||||||
namespace=self.network.namespace,
|
namespace=self.network.namespace,
|
||||||
|
service=DNSMASQ_SERVICE_NAME,
|
||||||
default_cmd_callback=cmd_callback,
|
default_cmd_callback=cmd_callback,
|
||||||
pid_file=self.get_conf_file_name('pid'),
|
pid_file=self.get_conf_file_name('pid'),
|
||||||
run_as_root=True)
|
run_as_root=True)
|
||||||
|
@ -66,6 +66,7 @@ class ProcessManager(MonitoredProcess):
|
|||||||
self.pid_file = pid_file
|
self.pid_file = pid_file
|
||||||
self.run_as_root = run_as_root or self.namespace is not None
|
self.run_as_root = run_as_root or self.namespace is not None
|
||||||
self.custom_reload_callback = custom_reload_callback
|
self.custom_reload_callback = custom_reload_callback
|
||||||
|
self.kill_scripts_path = cfg.CONF.AGENT.kill_scripts_path
|
||||||
|
|
||||||
if service:
|
if service:
|
||||||
self.service_pid_fname = 'pid.' + service
|
self.service_pid_fname = 'pid.' + service
|
||||||
@ -105,7 +106,7 @@ class ProcessManager(MonitoredProcess):
|
|||||||
ip_wrapper.netns.execute(cmd, addl_env=self.cmd_addl_env,
|
ip_wrapper.netns.execute(cmd, addl_env=self.cmd_addl_env,
|
||||||
run_as_root=self.run_as_root)
|
run_as_root=self.run_as_root)
|
||||||
else:
|
else:
|
||||||
cmd = ['kill', '-%s' % (sig), pid]
|
cmd = self.get_kill_cmd(sig, pid)
|
||||||
utils.execute(cmd, run_as_root=self.run_as_root)
|
utils.execute(cmd, run_as_root=self.run_as_root)
|
||||||
# In the case of shutting down, remove the pid file
|
# In the case of shutting down, remove the pid file
|
||||||
if sig == '9':
|
if sig == '9':
|
||||||
@ -117,6 +118,13 @@ class ProcessManager(MonitoredProcess):
|
|||||||
else:
|
else:
|
||||||
LOG.debug('No process started for %s', self.uuid)
|
LOG.debug('No process started for %s', self.uuid)
|
||||||
|
|
||||||
|
def get_kill_cmd(self, sig, pid):
|
||||||
|
if self.kill_scripts_path:
|
||||||
|
kill_file = "%s-kill" % self.service
|
||||||
|
if os.path.isfile(os.path.join(self.kill_scripts_path, kill_file)):
|
||||||
|
return [kill_file, sig, pid]
|
||||||
|
return ['kill', '-%s' % (sig), pid]
|
||||||
|
|
||||||
def get_pid_file_name(self):
|
def get_pid_file_name(self):
|
||||||
"""Returns the file name for a given kind of config file."""
|
"""Returns the file name for a given kind of config file."""
|
||||||
if self.pid_file:
|
if self.pid_file:
|
||||||
|
@ -457,6 +457,7 @@ class KeepalivedManager(object):
|
|||||||
cfg.CONF,
|
cfg.CONF,
|
||||||
self.resource_id,
|
self.resource_id,
|
||||||
self.namespace,
|
self.namespace,
|
||||||
|
service=KEEPALIVED_SERVICE_NAME,
|
||||||
pids_path=self.conf_path)
|
pids_path=self.conf_path)
|
||||||
|
|
||||||
def _get_vrrp_process(self, pid_file):
|
def _get_vrrp_process(self, pid_file):
|
||||||
|
@ -35,6 +35,7 @@ from neutron.agent.linux import external_process
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
METADATA_SERVICE_NAME = 'metadata-proxy'
|
METADATA_SERVICE_NAME = 'metadata-proxy'
|
||||||
|
HAPROXY_SERVICE = 'haproxy'
|
||||||
|
|
||||||
PROXY_CONFIG_DIR = "ns-metadata-proxy"
|
PROXY_CONFIG_DIR = "ns-metadata-proxy"
|
||||||
_HAPROXY_CONFIG_TEMPLATE = """
|
_HAPROXY_CONFIG_TEMPLATE = """
|
||||||
@ -220,7 +221,7 @@ class MetadataDriver(object):
|
|||||||
conf.state_path,
|
conf.state_path,
|
||||||
pid_file)
|
pid_file)
|
||||||
haproxy.create_config_file()
|
haproxy.create_config_file()
|
||||||
proxy_cmd = ['haproxy',
|
proxy_cmd = [HAPROXY_SERVICE,
|
||||||
'-f', haproxy.cfg_path]
|
'-f', haproxy.cfg_path]
|
||||||
return proxy_cmd
|
return proxy_cmd
|
||||||
|
|
||||||
@ -260,6 +261,7 @@ class MetadataDriver(object):
|
|||||||
conf=conf,
|
conf=conf,
|
||||||
uuid=router_id,
|
uuid=router_id,
|
||||||
namespace=ns_name,
|
namespace=ns_name,
|
||||||
|
service=HAPROXY_SERVICE,
|
||||||
default_cmd_callback=callback)
|
default_cmd_callback=callback)
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,6 +145,15 @@ PROCESS_MONITOR_OPTS = [
|
|||||||
cfg.IntOpt('check_child_processes_interval', default=60,
|
cfg.IntOpt('check_child_processes_interval', default=60,
|
||||||
help=_('Interval between checks of child process liveness '
|
help=_('Interval between checks of child process liveness '
|
||||||
'(seconds), use 0 to disable')),
|
'(seconds), use 0 to disable')),
|
||||||
|
cfg.StrOpt('kill_scripts_path', default='/etc/neutron/kill_scripts/',
|
||||||
|
help=_('Location of scripts used to kill external processes. '
|
||||||
|
'Names of scripts here must follow the pattern: '
|
||||||
|
'"<process-name>-kill" where <process-name> is name of '
|
||||||
|
'the process which should be killed using this script. '
|
||||||
|
'For example, kill script for dnsmasq process should be '
|
||||||
|
'named "dnsmasq-kill". '
|
||||||
|
'If path is set to None, then default "kill" command '
|
||||||
|
'will be used to stop processes.')),
|
||||||
]
|
]
|
||||||
|
|
||||||
AVAILABILITY_ZONE_OPTS = [
|
AVAILABILITY_ZONE_OPTS = [
|
||||||
|
@ -34,6 +34,7 @@ from neutron.agent.linux import external_process
|
|||||||
from neutron.agent.linux import interface
|
from neutron.agent.linux import interface
|
||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.agent.linux import keepalived
|
from neutron.agent.linux import keepalived
|
||||||
|
from neutron.agent.metadata import driver as metadata_driver
|
||||||
from neutron.common import utils as common_utils
|
from neutron.common import utils as common_utils
|
||||||
from neutron.conf.agent import common as agent_config
|
from neutron.conf.agent import common as agent_config
|
||||||
from neutron.conf import common as common_config
|
from neutron.conf import common as common_config
|
||||||
@ -398,7 +399,8 @@ class L3AgentTestFramework(base.BaseSudoTestCase):
|
|||||||
pm = external_process.ProcessManager(
|
pm = external_process.ProcessManager(
|
||||||
conf,
|
conf,
|
||||||
router.router_id,
|
router.router_id,
|
||||||
router.ns_name)
|
router.ns_name,
|
||||||
|
service=metadata_driver.HAPROXY_SERVICE)
|
||||||
return pm.active
|
return pm.active
|
||||||
|
|
||||||
def device_exists_with_ips_and_mac(self, expected_device, name_getter,
|
def device_exists_with_ips_and_mac(self, expected_device, name_getter,
|
||||||
|
@ -32,6 +32,7 @@ from neutron.agent.linux import external_process
|
|||||||
from neutron.agent.linux import interface
|
from neutron.agent.linux import interface
|
||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.agent.linux import utils
|
from neutron.agent.linux import utils
|
||||||
|
from neutron.agent.metadata import driver as metadata_driver
|
||||||
from neutron.common import utils as common_utils
|
from neutron.common import utils as common_utils
|
||||||
from neutron.conf.agent import common as config
|
from neutron.conf.agent import common as config
|
||||||
from neutron.tests.common import net_helpers
|
from neutron.tests.common import net_helpers
|
||||||
@ -256,7 +257,8 @@ class DHCPAgentOVSTestFramework(base.BaseSudoTestCase):
|
|||||||
return external_process.ProcessManager(
|
return external_process.ProcessManager(
|
||||||
self.conf,
|
self.conf,
|
||||||
network.id,
|
network.id,
|
||||||
network.namespace)
|
network.namespace,
|
||||||
|
service=metadata_driver.HAPROXY_SERVICE)
|
||||||
|
|
||||||
|
|
||||||
class DHCPAgentOVSTestCase(DHCPAgentOVSTestFramework):
|
class DHCPAgentOVSTestCase(DHCPAgentOVSTestFramework):
|
||||||
|
@ -693,6 +693,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase):
|
|||||||
return mock.call(conf=cfg.CONF,
|
return mock.call(conf=cfg.CONF,
|
||||||
uuid=FAKE_NETWORK_UUID,
|
uuid=FAKE_NETWORK_UUID,
|
||||||
namespace=ns,
|
namespace=ns,
|
||||||
|
service='haproxy',
|
||||||
default_cmd_callback=mock.ANY)
|
default_cmd_callback=mock.ANY)
|
||||||
|
|
||||||
def _enable_dhcp_helper(self, network, enable_isolated_metadata=False,
|
def _enable_dhcp_helper(self, network, enable_isolated_metadata=False,
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
@ -244,6 +245,48 @@ class TestProcessManager(base.BaseTestCase):
|
|||||||
manager.disable()
|
manager.disable()
|
||||||
debug.assert_called_once_with(mock.ANY, mock.ANY)
|
debug.assert_called_once_with(mock.ANY, mock.ANY)
|
||||||
|
|
||||||
|
def _test_disable_custom_kill_script(self, kill_script_exists, namespace,
|
||||||
|
kill_scripts_path='test-path/'):
|
||||||
|
cfg.CONF.set_override("kill_scripts_path", kill_scripts_path, "AGENT")
|
||||||
|
if kill_script_exists:
|
||||||
|
expected_cmd = ['test-service-kill', '9', 4]
|
||||||
|
else:
|
||||||
|
expected_cmd = ['kill', '-9', 4]
|
||||||
|
|
||||||
|
with mock.patch.object(ep.ProcessManager, 'pid') as pid:
|
||||||
|
pid.__get__ = mock.Mock(return_value=4)
|
||||||
|
with mock.patch.object(ep.ProcessManager, 'active') as active:
|
||||||
|
active.__get__ = mock.Mock(return_value=True)
|
||||||
|
manager = ep.ProcessManager(
|
||||||
|
self.conf, 'uuid', namespace=namespace,
|
||||||
|
service='test-service')
|
||||||
|
with mock.patch.object(ep, 'utils') as utils, \
|
||||||
|
mock.patch.object(os.path, 'isfile',
|
||||||
|
return_value=kill_script_exists):
|
||||||
|
manager.disable()
|
||||||
|
utils.execute.assert_called_with(
|
||||||
|
expected_cmd, run_as_root=bool(namespace))
|
||||||
|
|
||||||
|
def test_disable_custom_kill_script_no_namespace(self):
|
||||||
|
self._test_disable_custom_kill_script(
|
||||||
|
kill_script_exists=True, namespace=None)
|
||||||
|
|
||||||
|
def test_disable_custom_kill_script_namespace(self):
|
||||||
|
self._test_disable_custom_kill_script(
|
||||||
|
kill_script_exists=True, namespace="ns")
|
||||||
|
|
||||||
|
def test_disable_custom_kill_script_no_kill_script_no_namespace(self):
|
||||||
|
self._test_disable_custom_kill_script(
|
||||||
|
kill_script_exists=False, namespace=None)
|
||||||
|
|
||||||
|
def test_disable_custom_kill_script_no_kill_script_namespace(self):
|
||||||
|
self._test_disable_custom_kill_script(
|
||||||
|
kill_script_exists=False, namespace="ns")
|
||||||
|
|
||||||
|
def test_disable_custom_kill_script_namespace_no_path(self):
|
||||||
|
self._test_disable_custom_kill_script(
|
||||||
|
kill_script_exists=False, namespace="ns", kill_scripts_path=None)
|
||||||
|
|
||||||
def test_get_pid_file_name_default(self):
|
def test_get_pid_file_name_default(self):
|
||||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||||
retval = manager.get_pid_file_name()
|
retval = manager.get_pid_file_name()
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for custom scripts used to kill external processes managed by
|
||||||
|
neutron agents, such as ``dnsmasq`` or ``keepalived``. Such custom scripts,
|
||||||
|
if defined, will be used instead default ``kill`` command to kill such
|
||||||
|
external processes.
|
Loading…
Reference in New Issue
Block a user