diff --git a/bindep.txt b/bindep.txt index 7e1f8dd536b..fd9b2172eff 100644 --- a/bindep.txt +++ b/bindep.txt @@ -8,6 +8,7 @@ gettext [test] # OpenStack infra that need these like # periodic-neutron-py27-with-oslo-master and # periodic-neutron-py35-with-neutron-lib-master. +haproxy libmysqlclient-dev [platform:dpkg test] mysql [platform:rpm test] mysql-client [platform:dpkg test] diff --git a/etc/neutron/rootwrap.d/dhcp.filters b/etc/neutron/rootwrap.d/dhcp.filters index 24404f59975..d48d2eac2be 100644 --- a/etc/neutron/rootwrap.d/dhcp.filters +++ b/etc/neutron/rootwrap.d/dhcp.filters @@ -22,9 +22,13 @@ mm-ctl: CommandFilter, mm-ctl, root dhcp_release: CommandFilter, dhcp_release, root dhcp_release6: CommandFilter, dhcp_release6, root -# metadata proxy -metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root +# haproxy +haproxy: RegExpFilter, haproxy, root, haproxy, -f, .* +kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP # RHEL invocation of the metadata proxy will report /usr/bin/python +# TODO(dalvarez): Remove kill_metadata* filters in Q release since +# neutron-ns-metadata-proxy is now replaced by haproxy. We keep them for now +# for the migration process kill_metadata: KillFilter, root, python, -9 kill_metadata7: KillFilter, root, python2.7, -9 kill_metadata35: KillFilter, root, python3.5, -9 diff --git a/etc/neutron/rootwrap.d/l3.filters b/etc/neutron/rootwrap.d/l3.filters index de4590ecf71..a0a86e600c4 100644 --- a/etc/neutron/rootwrap.d/l3.filters +++ b/etc/neutron/rootwrap.d/l3.filters @@ -16,9 +16,13 @@ sysctl: CommandFilter, sysctl, root route: CommandFilter, route, root radvd: CommandFilter, radvd, root -# metadata proxy -metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root +# haproxy +haproxy: RegExpFilter, haproxy, root, haproxy, -f, .* +kill_haproxy: KillFilter, root, haproxy, -15, -9, -HUP # RHEL invocation of the metadata proxy will report /usr/bin/python +# TODO(dalvarez): Remove kill_metadata* filters in Q release since +# neutron-ns-metadata-proxy is now replaced by haproxy. We keep them for now +# for the migration process kill_metadata: KillFilter, root, python, -15, -9 kill_metadata7: KillFilter, root, python2.7, -15, -9 kill_metadata35: KillFilter, root, python3.5, -15, -9 diff --git a/neutron/agent/common/config.py b/neutron/agent/common/config.py index 54f606168f5..9bffe7a76b5 100644 --- a/neutron/agent/common/config.py +++ b/neutron/agent/common/config.py @@ -122,8 +122,6 @@ def get_log_args(conf, log_file_name, **kwargs): log_dir = os.path.dirname(conf.log_file) if log_dir: cmd_args.append('--log-dir=%s' % log_dir) - if kwargs.get('metadata_proxy_watch_log') is False: - cmd_args.append('--nometadata_proxy_watch_log') else: if conf.use_syslog: cmd_args.append('--use-syslog') diff --git a/neutron/agent/dhcp_agent.py b/neutron/agent/dhcp_agent.py index a8269d8ac55..ff34405019e 100644 --- a/neutron/agent/dhcp_agent.py +++ b/neutron/agent/dhcp_agent.py @@ -33,7 +33,6 @@ def register_options(conf): config.register_agent_state_opts_helper(conf) config.register_availability_zone_opts_helper(conf) dhcp_config.register_agent_dhcp_opts(conf) - meta_conf.register_meta_conf_opts(meta_conf.DRIVER_OPTS, conf) meta_conf.register_meta_conf_opts(meta_conf.SHARED_OPTS, conf) conf.register_opts(interface.OPTS) diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index eadf490389b..810bae56f94 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -35,7 +35,6 @@ from neutron import service as neutron_service def register_opts(conf): l3_config.register_l3_agent_config_opts(l3_config.OPTS, conf) ha_conf.register_l3_agent_ha_opts(conf) - meta_conf.register_meta_conf_opts(meta_conf.DRIVER_OPTS, conf) meta_conf.register_meta_conf_opts(meta_conf.SHARED_OPTS, conf) config.register_interface_driver_opts_helper(conf) config.register_agent_state_opts_helper(conf) diff --git a/neutron/agent/linux/external_process.py b/neutron/agent/linux/external_process.py index 4dc3e642f56..4e4946228d9 100644 --- a/neutron/agent/linux/external_process.py +++ b/neutron/agent/linux/external_process.py @@ -138,16 +138,21 @@ class ProcessManager(MonitoredProcess): @property def active(self): + cmdline = self.cmdline + return self.uuid in cmdline if cmdline else False + + @property + def cmdline(self): pid = self.pid - if pid is None: - return False + if not pid: + return cmdline = '/proc/%s/cmdline' % pid try: with open(cmdline, "r") as f: - return self.uuid in f.readline() + return f.readline() except IOError: - return False + return ServiceId = collections.namedtuple('ServiceId', ['uuid', 'service']) diff --git a/neutron/agent/metadata/driver.py b/neutron/agent/metadata/driver.py index 92e4ba287be..5dd963eb360 100644 --- a/neutron/agent/metadata/driver.py +++ b/neutron/agent/metadata/driver.py @@ -13,23 +13,147 @@ # License for the specific language governing permissions and limitations # under the License. +import errno +import grp import os +import pwd -from neutron.agent.common import config +from oslo_config import cfg +from oslo_log import log as logging + +from neutron._i18n import _ from neutron.agent.l3 import ha_router from neutron.agent.l3 import namespaces from neutron.agent.linux import external_process -from neutron.agent.linux import utils from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources from neutron.common import constants from neutron.common import exceptions +LOG = logging.getLogger(__name__) # Access with redirection to metadata proxy iptables mark mask METADATA_SERVICE_NAME = 'metadata-proxy' +PROXY_CONFIG_DIR = "ns-metadata-proxy" +_HAPROXY_CONFIG_TEMPLATE = """ +global + log /dev/log local0 %(log_level)s + user %(user)s + group %(group)s + maxconn 1024 + pidfile %(pidfile)s + daemon + +defaults + log global + mode http + option httplog + option dontlognull + option http-server-close + option forwardfor + retries 3 + timeout http-request 30s + timeout connect 30s + timeout client 32s + timeout server 32s + timeout http-keep-alive 30s + +listen listener + bind 0.0.0.0:%(port)s + server metadata %(unix_socket_path)s + http-request add-header X-Neutron-%(res_type)s-ID %(res_id)s +""" + + +class InvalidUserOrGroupException(Exception): + pass + + +class HaproxyConfigurator(object): + def __init__(self, network_id, router_id, unix_socket_path, port, user, + group, state_path, pid_file): + self.network_id = network_id + self.router_id = router_id + if network_id is None and router_id is None: + raise exceptions.NetworkIdOrRouterIdRequiredError() + + self.port = port + self.user = user + self.group = group + self.state_path = state_path + self.unix_socket_path = unix_socket_path + self.pidfile = pid_file + self.log_level = 'debug' if cfg.CONF.debug else 'info' + + def create_config_file(self): + """Create the config file for haproxy.""" + # Need to convert uid/gid into username/group + try: + username = pwd.getpwuid(int(self.user)).pw_name + except (ValueError, KeyError): + try: + username = pwd.getpwnam(self.user).pw_name + except KeyError: + raise InvalidUserOrGroupException( + _("Invalid user/uid: '%s'") % self.user) + + try: + groupname = grp.getgrgid(int(self.group)).gr_name + except (ValueError, KeyError): + try: + groupname = grp.getgrnam(self.group).gr_name + except KeyError: + raise InvalidUserOrGroupException( + _("Invalid group/gid: '%s'") % self.group) + + cfg_info = { + 'port': self.port, + 'unix_socket_path': self.unix_socket_path, + 'user': username, + 'group': groupname, + 'pidfile': self.pidfile, + 'log_level': self.log_level + } + if self.network_id: + cfg_info['res_type'] = 'Network' + cfg_info['res_id'] = self.network_id + else: + cfg_info['res_type'] = 'Router' + cfg_info['res_id'] = self.router_id + + haproxy_cfg = _HAPROXY_CONFIG_TEMPLATE % cfg_info + LOG.debug("haproxy_cfg = %s", haproxy_cfg) + cfg_dir = self.get_config_path(self.state_path) + # uuid has to be included somewhere in the command line so that it can + # be tracked by process_monitor. + self.cfg_path = os.path.join(cfg_dir, "%s.conf" % cfg_info['res_id']) + if not os.path.exists(cfg_dir): + os.makedirs(cfg_dir) + with open(self.cfg_path, "w") as cfg_file: + cfg_file.write(haproxy_cfg) + + @staticmethod + def get_config_path(state_path): + return os.path.join(state_path or cfg.CONF.state_path, + PROXY_CONFIG_DIR) + + @staticmethod + def cleanup_config_file(uuid, state_path): + """Delete config file created when metadata proxy was spawned.""" + # Delete config file if it exists + cfg_path = os.path.join( + HaproxyConfigurator.get_config_path(state_path), + "%s.conf" % uuid) + try: + os.unlink(cfg_path) + except OSError as ex: + # It can happen that this function is called but metadata proxy + # was never spawned so its config file won't exist + if ex.errno != errno.ENOENT: + raise + class MetadataDriver(object): @@ -72,45 +196,30 @@ class MetadataDriver(object): 'port': port})] @classmethod - def _get_metadata_proxy_user_group_watchlog(cls, conf): + def _get_metadata_proxy_user_group(cls, conf): user = conf.metadata_proxy_user or str(os.geteuid()) group = conf.metadata_proxy_group or str(os.getegid()) - watch_log = conf.metadata_proxy_watch_log - if watch_log is None: - # NOTE(cbrandily): Commonly, log watching can be enabled only - # when metadata proxy user is agent effective user (id/name). - watch_log = utils.is_effective_user(user) - - return user, group, watch_log + return user, group @classmethod def _get_metadata_proxy_callback(cls, port, conf, network_id=None, router_id=None): - uuid = network_id or router_id - if uuid is None: - raise exceptions.NetworkIdOrRouterIdRequiredError() - - if network_id: - lookup_param = '--network_id=%s' % network_id - else: - lookup_param = '--router_id=%s' % router_id - def callback(pid_file): metadata_proxy_socket = conf.metadata_proxy_socket - user, group, watch_log = ( - cls._get_metadata_proxy_user_group_watchlog(conf)) - proxy_cmd = ['neutron-ns-metadata-proxy', - '--pid_file=%s' % pid_file, - '--metadata_proxy_socket=%s' % metadata_proxy_socket, - lookup_param, - '--state_path=%s' % conf.state_path, - '--metadata_port=%s' % port, - '--metadata_proxy_user=%s' % user, - '--metadata_proxy_group=%s' % group] - proxy_cmd.extend(config.get_log_args( - conf, 'neutron-ns-metadata-proxy-%s.log' % uuid, - metadata_proxy_watch_log=watch_log)) + user, group = ( + cls._get_metadata_proxy_user_group(conf)) + haproxy = HaproxyConfigurator(network_id, + router_id, + metadata_proxy_socket, + port, + user, + group, + conf.state_path, + pid_file) + haproxy.create_config_file() + proxy_cmd = ['haproxy', + '-f', haproxy.cfg_path] return proxy_cmd return callback @@ -124,16 +233,41 @@ class MetadataDriver(object): pm = cls._get_metadata_proxy_process_manager(uuid, conf, ns_name=ns_name, callback=callback) + # TODO(dalvarez): Remove in Q cycle. This will kill running instances + # of old ns-metadata-proxy Python version in order to be replaced by + # haproxy. This will help with upgrading and shall be removed in next + # cycle. + cls._migrate_python_ns_metadata_proxy_if_needed(pm) + pm.enable() monitor.register(uuid, METADATA_SERVICE_NAME, pm) cls.monitors[router_id] = pm + @staticmethod + def _migrate_python_ns_metadata_proxy_if_needed(pm): + """Kill running Python version of ns-metadata-proxy. + + This function will detect if the current metadata proxy process is + running the old Python version and kill it so that the new haproxy + version is spawned instead. + """ + # Read cmdline to a local var to avoid reading twice from /proc file + cmdline = pm.cmdline + if cmdline and 'haproxy' not in cmdline: + LOG.debug("Migrating old instance of python ns-metadata proxy to " + "new one based on haproxy (%s)", cmdline) + pm.disable() + @classmethod def destroy_monitored_metadata_proxy(cls, monitor, uuid, conf): monitor.unregister(uuid, METADATA_SERVICE_NAME) # No need to pass ns name as it's not needed for disable() pm = cls._get_metadata_proxy_process_manager(uuid, conf) pm.disable() + + # Delete metadata proxy config file + HaproxyConfigurator.cleanup_config_file(uuid, cfg.CONF.state_path) + cls.monitors.pop(uuid, None) @classmethod diff --git a/neutron/agent/metadata/namespace_proxy.py b/neutron/agent/metadata/namespace_proxy.py deleted file mode 100644 index 7a484de0f48..00000000000 --- a/neutron/agent/metadata/namespace_proxy.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2012 New Dream Network, LLC (DreamHost) -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import httplib2 -from oslo_config import cfg -from oslo_log import log as logging -from oslo_service import wsgi as base_wsgi -from oslo_utils import encodeutils -import six -import six.moves.urllib.parse as urlparse -import webob - -from neutron._i18n import _, _LE -from neutron.agent.linux import daemon -from neutron.agent.linux import utils as agent_utils -from neutron.common import config -from neutron.common import exceptions -from neutron.common import utils -from neutron.conf.agent.metadata import namespace_proxy as namespace -from neutron import wsgi - -LOG = logging.getLogger(__name__) - - -class NetworkMetadataProxyHandler(object): - """Proxy AF_INET metadata request through Unix Domain socket. - - The Unix domain socket allows the proxy access resource that are not - accessible within the isolated tenant context. - """ - - def __init__(self, network_id=None, router_id=None): - self.network_id = network_id - self.router_id = router_id - - if network_id is None and router_id is None: - raise exceptions.NetworkIdOrRouterIdRequiredError() - - @webob.dec.wsgify(RequestClass=base_wsgi.Request) - def __call__(self, req): - LOG.debug("Request: %s", req) - try: - return self._proxy_request(req.remote_addr, - req.method, - req.path_info, - req.query_string, - req.body) - except Exception: - LOG.exception(_LE("Unexpected error.")) - msg = _('An unknown error has occurred. ' - 'Please try your request again.') - explanation = six.text_type(msg) - return webob.exc.HTTPInternalServerError(explanation=explanation) - - def _proxy_request(self, remote_address, method, path_info, - query_string, body): - headers = { - 'X-Forwarded-For': remote_address, - } - - if self.router_id: - headers['X-Neutron-Router-ID'] = self.router_id - else: - headers['X-Neutron-Network-ID'] = self.network_id - - url = urlparse.urlunsplit(( - 'http', - '169.254.169.254', # a dummy value to make the request proper - path_info, - query_string, - '')) - - h = httplib2.Http() - resp, content = h.request( - url, - method=method, - headers=headers, - body=body, - connection_type=agent_utils.UnixDomainHTTPConnection) - - if resp.status == 200: - LOG.debug(resp) - LOG.debug(encodeutils.safe_decode(content, errors='replace')) - response = webob.Response() - response.status = resp.status - response.headers['Content-Type'] = resp['content-type'] - response.body = wsgi.encode_body(content) - return response - elif resp.status == 400: - return webob.exc.HTTPBadRequest() - elif resp.status == 404: - return webob.exc.HTTPNotFound() - elif resp.status == 409: - return webob.exc.HTTPConflict() - elif resp.status == 500: - msg = _( - 'Remote metadata server experienced an internal server error.' - ) - LOG.debug(msg) - explanation = six.text_type(msg) - return webob.exc.HTTPInternalServerError(explanation=explanation) - else: - raise Exception(_('Unexpected response code: %s') % resp.status) - - -class ProxyDaemon(daemon.Daemon): - def __init__(self, pidfile, port, network_id=None, router_id=None, - user=None, group=None, watch_log=True): - uuid = network_id or router_id - super(ProxyDaemon, self).__init__(pidfile, uuid=uuid, user=user, - group=group, watch_log=watch_log) - self.network_id = network_id - self.router_id = router_id - self.port = port - - def run(self): - handler = NetworkMetadataProxyHandler( - self.network_id, - self.router_id) - proxy = wsgi.Server('neutron-network-metadata-proxy') - proxy.start(handler, self.port) - - # Drop privileges after port bind - super(ProxyDaemon, self).run() - - proxy.wait() - - -def main(): - namespace.register_namespace_proxy_opts(cfg.CONF) - # Don't read any default configuration file, just handle cmdline opts - cfg.CONF(project='neutron', - default_config_files=[], default_config_dirs=[]) - config.setup_logging() - utils.log_opt_values(LOG) - - proxy = ProxyDaemon(cfg.CONF.pid_file, - cfg.CONF.metadata_port, - network_id=cfg.CONF.network_id, - router_id=cfg.CONF.router_id, - user=cfg.CONF.metadata_proxy_user, - group=cfg.CONF.metadata_proxy_group, - watch_log=cfg.CONF.metadata_proxy_watch_log) - - if cfg.CONF.daemonize: - proxy.start() - else: - proxy.run() diff --git a/neutron/cmd/eventlet/agents/metadata_proxy.py b/neutron/cmd/eventlet/agents/metadata_proxy.py deleted file mode 100644 index dc61c144528..00000000000 --- a/neutron/cmd/eventlet/agents/metadata_proxy.py +++ /dev/null @@ -1,17 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from neutron.agent.metadata import namespace_proxy - - -def main(): - namespace_proxy.main() diff --git a/neutron/conf/agent/metadata/config.py b/neutron/conf/agent/metadata/config.py index 5f521f4e7e0..f72d56d40b7 100644 --- a/neutron/conf/agent/metadata/config.py +++ b/neutron/conf/agent/metadata/config.py @@ -40,20 +40,6 @@ SHARED_OPTS = [ ] -DRIVER_OPTS = [ - cfg.BoolOpt('metadata_proxy_watch_log', - help=_("Enable/Disable log watch by metadata proxy. It " - "should be disabled when metadata_proxy_user/group " - "is not allowed to read/write its log file and " - "copytruncate logrotate option must be used if " - "logrotate is enabled on metadata proxy log " - "files. Option default value is deduced from " - "metadata_proxy_user: watch log is enabled if " - "metadata_proxy_user is agent effective user " - "id/name.")), -] - - METADATA_PROXY_HANDLER_OPTS = [ cfg.StrOpt('auth_ca_cert', help=_("Certificate Authority public key (CA cert) " diff --git a/neutron/conf/agent/metadata/namespace_proxy.py b/neutron/conf/agent/metadata/namespace_proxy.py deleted file mode 100644 index 66160c2cc93..00000000000 --- a/neutron/conf/agent/metadata/namespace_proxy.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2016 New Dream Network, LLC (DreamHost) -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslo_config import cfg - -from neutron._i18n import _ - -OPTS = [ - cfg.StrOpt('network_id', - help=_('Network that will have instance metadata ' - 'proxied.')), - cfg.StrOpt('router_id', - help=_('Router that will have connected instances\' ' - 'metadata proxied.')), - cfg.StrOpt('pid_file', - help=_('Location of pid file of this process.')), - cfg.BoolOpt('daemonize', - default=True, - help=_('Run as daemon.')), - cfg.PortOpt('metadata_port', - default=9697, - help=_('TCP Port to listen for metadata server' - 'requests.')), - cfg.StrOpt('metadata_proxy_socket', - default='$state_path/metadata_proxy', - help=_('Location of Metadata Proxy UNIX domain ' - 'socket')), - cfg.StrOpt('metadata_proxy_user', - help=_('User (uid or name) running metadata proxy after ' - 'its initialization')), - cfg.StrOpt('metadata_proxy_group', - help=_('Group (gid or name) running metadata proxy after ' - 'its initialization')), - cfg.BoolOpt('metadata_proxy_watch_log', - default=True, - help=_('Watch file log. Log watch should be disabled when ' - 'metadata_proxy_user/group has no read/write ' - 'permissions on metadata proxy log file.')), -] - - -def register_namespace_proxy_opts(cfg=cfg.CONF): - cfg.register_cli_opts(OPTS) diff --git a/neutron/opts.py b/neutron/opts.py index 6ea48134d55..3cd9caf4548 100644 --- a/neutron/opts.py +++ b/neutron/opts.py @@ -89,8 +89,7 @@ def list_agent_opts(): ('DEFAULT', itertools.chain( neutron.agent.common.config.INTERFACE_DRIVER_OPTS, - neutron.conf.agent.metadata.config.SHARED_OPTS, - neutron.conf.agent.metadata.config.DRIVER_OPTS) + neutron.conf.agent.metadata.config.SHARED_OPTS) ) ] diff --git a/neutron/tests/functional/agent/l3/framework.py b/neutron/tests/functional/agent/l3/framework.py index 35a2b259778..27e8b8af2ec 100644 --- a/neutron/tests/functional/agent/l3/framework.py +++ b/neutron/tests/functional/agent/l3/framework.py @@ -83,8 +83,6 @@ class L3AgentTestFramework(base.BaseSudoTestCase): get_temp_file_path = functools.partial(self.get_temp_file_path, root=temp_dir) conf.set_override('state_path', temp_dir.path) - # NOTE(cbrandily): log_file or log_dir must be set otherwise - # metadata_proxy_watch_log has no effect conf.set_override('log_file', get_temp_file_path('log_file')) conf.set_override('metadata_proxy_socket', diff --git a/neutron/tests/functional/agent/l3/test_metadata_proxy.py b/neutron/tests/functional/agent/l3/test_metadata_proxy.py index fb0aa973fae..cd4e9dd3aeb 100644 --- a/neutron/tests/functional/agent/l3/test_metadata_proxy.py +++ b/neutron/tests/functional/agent/l3/test_metadata_proxy.py @@ -12,20 +12,24 @@ # License for the specific language governing permissions and limitations # under the License. +import functools import os.path import time +import fixtures +from oslo_config import cfg import webob import webob.dec import webob.exc from neutron.agent.linux import dhcp +from neutron.agent.linux import external_process from neutron.agent.linux import utils from neutron.tests.common import machine_fixtures from neutron.tests.common import net_helpers from neutron.tests.functional.agent.l3 import framework from neutron.tests.functional.agent.linux import helpers - +from neutron.tests.functional.agent.linux import simple_daemon METADATA_REQUEST_TIMEOUT = 60 METADATA_REQUEST_SLEEP = 5 @@ -118,6 +122,57 @@ class MetadataL3AgentTestCase(framework.L3AgentTestFramework): # Check status code self.assertIn(str(webob.exc.HTTPOk.code), firstline.split()) + @staticmethod + def _make_cmdline_callback(uuid): + def _cmdline_callback(pidfile): + cmdline = ["python", simple_daemon.__file__, + "--uuid=%s" % uuid, + "--pid_file=%s" % pidfile] + return cmdline + return _cmdline_callback + + def test_haproxy_migration_path(self): + """Test the migration path for haproxy. + + This test will launch the simple_daemon Python process before spawning + haproxy. When launching haproxy, it will be detected and killed, as + it's running on the same pidfile and with the router uuid in its + cmdline. + """ + # Make sure that external_pids configuration option is the same for + # simple_daemon and haproxy so that both work on the same pid_file. + get_temp_file_path = functools.partial( + self.get_temp_file_path, + root=self.useFixture(fixtures.TempDir())) + cfg.CONF.set_override('external_pids', + get_temp_file_path('external/pids')) + self.agent.conf.set_override('external_pids', + get_temp_file_path('external/pids')) + + router_info = self.generate_router_info(enable_ha=False) + + # Spawn the simple_daemon process in the background using the generated + # router uuid. We are not registering it within ProcessMonitor so that + # it doesn't get respawned once killed. + _callback = self._make_cmdline_callback(router_info['id']) + pm = external_process.ProcessManager( + conf=cfg.CONF, + uuid=router_info['id'], + default_cmd_callback=_callback) + pm.enable() + self.addCleanup(pm.disable) + + # Make sure that simple_daemon is running + self.assertIn('simple_daemon', pm.cmdline) + + # Create the router. This is expected to launch haproxy after killing + # the simple_daemon process. + self.manage_router(self.agent, router_info) + + # Make sure that it was killed and replaced by haproxy + self.assertNotIn('simple_daemon', pm.cmdline) + self.assertIn('haproxy', pm.cmdline) + class UnprivilegedUserMetadataL3AgentTestCase(MetadataL3AgentTestCase): """Test metadata proxy with least privileged user. @@ -131,7 +186,6 @@ class UnprivilegedUserMetadataL3AgentTestCase(MetadataL3AgentTestCase): def setUp(self): super(UnprivilegedUserMetadataL3AgentTestCase, self).setUp() self.agent.conf.set_override('metadata_proxy_user', '65534') - self.agent.conf.set_override('metadata_proxy_watch_log', False) class UnprivilegedUserGroupMetadataL3AgentTestCase(MetadataL3AgentTestCase): @@ -149,4 +203,3 @@ class UnprivilegedUserGroupMetadataL3AgentTestCase(MetadataL3AgentTestCase): super(UnprivilegedUserGroupMetadataL3AgentTestCase, self).setUp() self.agent.conf.set_override('metadata_proxy_user', '65534') self.agent.conf.set_override('metadata_proxy_group', '65534') - self.agent.conf.set_override('metadata_proxy_watch_log', False) diff --git a/neutron/tests/unit/agent/dhcp/test_agent.py b/neutron/tests/unit/agent/dhcp/test_agent.py index 35cc7d12b21..b70e593ae2b 100644 --- a/neutron/tests/unit/agent/dhcp/test_agent.py +++ b/neutron/tests/unit/agent/dhcp/test_agent.py @@ -234,7 +234,9 @@ class TestDhcpAgent(base.BaseTestCase): self.driver_cls.return_value = self.driver self.mock_makedirs_p = mock.patch("os.makedirs") self.mock_makedirs = self.mock_makedirs_p.start() - + self.mock_create_metadata_proxy_cfg = mock.patch( + "neutron.agent.metadata.driver.HaproxyConfigurator") + self.mock_create_metadata_proxy_cfg.start() self.mock_ip_wrapper_p = mock.patch("neutron.agent.linux.ip_lib." "IPWrapper") self.mock_ip_wrapper = self.mock_ip_wrapper_p.start() @@ -676,7 +678,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): if is_isolated_network and enable_isolated_metadata: self.external_process.assert_has_calls([ self._process_manager_constructor_call(), - mock.call().enable()]) + mock.call().enable()], any_order=True) else: self.external_process.assert_has_calls([ self._process_manager_constructor_call(ns=None), @@ -842,7 +844,7 @@ class TestDhcpAgentEventHandler(base.BaseTestCase): self.external_process.assert_has_calls([ self._process_manager_constructor_call(), mock.call().enable() - ]) + ], any_order=True) def test_disable_isolated_metadata_proxy(self): method_path = ('neutron.agent.metadata.driver.MetadataDriver' diff --git a/neutron/tests/unit/agent/linux/test_external_process.py b/neutron/tests/unit/agent/linux/test_external_process.py index e746bf83e2d..962a5c360cf 100644 --- a/neutron/tests/unit/agent/linux/test_external_process.py +++ b/neutron/tests/unit/agent/linux/test_external_process.py @@ -25,6 +25,7 @@ from neutron.tests import tools TEST_UUID = 'test-uuid' TEST_SERVICE = 'testsvc' TEST_PID = 1234 +TEST_CMDLINE = 'python foo --router_id=%s' class BaseTestProcessMonitor(base.BaseTestCase): @@ -264,32 +265,42 @@ class TestProcessManager(base.BaseTestCase): self.assertIsNone(manager.pid) def test_active(self): - mock_open = self.useFixture( - tools.OpenFixture('/proc/4/cmdline', 'python foo --router_id=uuid') - ).mock_open - with mock.patch.object(ep.ProcessManager, 'pid') as pid: - pid.__get__ = mock.Mock(return_value=4) + with mock.patch.object(ep.ProcessManager, 'cmdline') as cmdline: + cmdline.__get__ = mock.Mock( + return_value=TEST_CMDLINE % 'uuid') manager = ep.ProcessManager(self.conf, 'uuid') self.assertTrue(manager.active) - mock_open.assert_called_once_with('/proc/4/cmdline', 'r') - def test_active_none(self): - dummy_cmd_line = 'python foo --router_id=uuid' - self.execute.return_value = dummy_cmd_line - with mock.patch.object(ep.ProcessManager, 'pid') as pid: - pid.__get__ = mock.Mock(return_value=None) + with mock.patch.object(ep.ProcessManager, 'cmdline') as cmdline: + cmdline.__get__ = mock.Mock(return_value=None) manager = ep.ProcessManager(self.conf, 'uuid') self.assertFalse(manager.active) def test_active_cmd_mismatch(self): + with mock.patch.object(ep.ProcessManager, 'cmdline') as cmdline: + cmdline.__get__ = mock.Mock( + return_value=TEST_CMDLINE % 'anotherid') + manager = ep.ProcessManager(self.conf, 'uuid') + self.assertFalse(manager.active) + + def test_cmdline(self): mock_open = self.useFixture( - tools.OpenFixture('/proc/4/cmdline', - 'python foo --router_id=anotherid') + tools.OpenFixture('/proc/4/cmdline', TEST_CMDLINE % 'uuid') ).mock_open with mock.patch.object(ep.ProcessManager, 'pid') as pid: pid.__get__ = mock.Mock(return_value=4) manager = ep.ProcessManager(self.conf, 'uuid') - self.assertFalse(manager.active) - + self.assertEqual(TEST_CMDLINE % 'uuid', manager.cmdline) + mock_open.assert_called_once_with('/proc/4/cmdline', 'r') + + def test_cmdline_none(self): + mock_open = self.useFixture( + tools.OpenFixture('/proc/4/cmdline', TEST_CMDLINE % 'uuid') + ).mock_open + mock_open.side_effect = IOError() + with mock.patch.object(ep.ProcessManager, 'pid') as pid: + pid.__get__ = mock.Mock(return_value=4) + manager = ep.ProcessManager(self.conf, 'uuid') + self.assertIsNone(manager.cmdline) mock_open.assert_called_once_with('/proc/4/cmdline', 'r') diff --git a/neutron/tests/unit/agent/metadata/test_driver.py b/neutron/tests/unit/agent/metadata/test_driver.py index 35b951e4b5c..05e587d302a 100644 --- a/neutron/tests/unit/agent/metadata/test_driver.py +++ b/neutron/tests/unit/agent/metadata/test_driver.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + import mock from oslo_config import cfg from oslo_utils import uuidutils @@ -26,7 +28,8 @@ from neutron.conf.agent.l3 import config as l3_config from neutron.conf.agent.l3 import ha as ha_conf from neutron.conf.agent.metadata import config as meta_conf from neutron.tests import base - +from neutron.tests import tools +from neutron.tests.unit.agent.linux import test_utils _uuid = uuidutils.generate_uuid @@ -60,9 +63,11 @@ class TestMetadataDriverRules(base.BaseTestCase): class TestMetadataDriverProcess(base.BaseTestCase): - EUID = 123 - EGID = 456 EUNAME = 'neutron' + EGNAME = 'neutron' + METADATA_PORT = 8080 + METADATA_SOCKET = '/socket/path' + PIDFILE = 'pidfile' def setUp(self): super(TestMetadataDriverProcess, self).setUp() @@ -78,7 +83,6 @@ class TestMetadataDriverProcess(base.BaseTestCase): l3_config.register_l3_agent_config_opts(l3_config.OPTS, cfg.CONF) ha_conf.register_l3_agent_ha_opts() meta_conf.register_meta_conf_opts(meta_conf.SHARED_OPTS, cfg.CONF) - meta_conf.register_meta_conf_opts(meta_conf.DRIVER_OPTS, cfg.CONF) def test_after_router_updated_called_on_agent_process_update(self): with mock.patch.object(metadata_driver, 'after_router_updated') as f,\ @@ -93,74 +97,105 @@ class TestMetadataDriverProcess(base.BaseTestCase): f.assert_called_once_with( 'router', 'after_update', agent, router=ri) - def _test_spawn_metadata_proxy(self, expected_user, expected_group, - user='', group='', watch_log=True): + def test_spawn_metadata_proxy(self): router_id = _uuid() router_ns = 'qrouter-%s' % router_id - metadata_port = 8080 ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper' - is_effective_user = 'neutron.agent.linux.utils.is_effective_user' - fake_is_effective_user = lambda x: x in [self.EUNAME, str(self.EUID)] - cfg.CONF.set_override('metadata_proxy_user', user) - cfg.CONF.set_override('metadata_proxy_group', group) - cfg.CONF.set_override('log_file', 'test.log') + cfg.CONF.set_override('metadata_proxy_user', self.EUNAME) + cfg.CONF.set_override('metadata_proxy_group', self.EGNAME) + cfg.CONF.set_override('metadata_proxy_socket', self.METADATA_SOCKET) cfg.CONF.set_override('debug', True) agent = l3_agent.L3NATAgent('localhost') - with mock.patch('os.geteuid', return_value=self.EUID),\ - mock.patch('os.getegid', return_value=self.EGID),\ - mock.patch(is_effective_user, - side_effect=fake_is_effective_user),\ - mock.patch(ip_class_path) as ip_mock: + with mock.patch(ip_class_path) as ip_mock,\ + mock.patch( + 'neutron.agent.linux.external_process.' + 'ProcessManager.get_pid_file_name', + return_value=self.PIDFILE),\ + mock.patch('pwd.getpwnam', + return_value=test_utils.FakeUser(self.EUNAME)),\ + mock.patch('grp.getgrnam', + return_value=test_utils.FakeGroup(self.EGNAME)),\ + mock.patch('os.makedirs'): + cfg_file = os.path.join( + metadata_driver.HaproxyConfigurator.get_config_path( + agent.conf.state_path), + "%s.conf" % router_id) + mock_open = self.useFixture( + tools.OpenFixture(cfg_file)).mock_open agent.metadata_driver.spawn_monitored_metadata_proxy( agent.process_monitor, router_ns, - metadata_port, + self.METADATA_PORT, agent.conf, router_id=router_id) + netns_execute_args = [ - 'neutron-ns-metadata-proxy', - mock.ANY, - mock.ANY, - '--router_id=%s' % router_id, - mock.ANY, - '--metadata_port=%s' % metadata_port, - '--metadata_proxy_user=%s' % expected_user, - '--metadata_proxy_group=%s' % expected_group, - '--debug', - '--log-file=neutron-ns-metadata-proxy-%s.log' % - router_id] - if not watch_log: - netns_execute_args.append( - '--nometadata_proxy_watch_log') + 'haproxy', + '-f', cfg_file] + + cfg_contents = metadata_driver._HAPROXY_CONFIG_TEMPLATE % { + 'user': self.EUNAME, + 'group': self.EGNAME, + 'port': self.METADATA_PORT, + 'unix_socket_path': self.METADATA_SOCKET, + 'res_type': 'Router', + 'res_id': router_id, + 'pidfile': self.PIDFILE, + 'log_level': 'debug'} + + mock_open.assert_has_calls([ + mock.call(cfg_file, 'w'), + mock.call().write(cfg_contents)], + any_order=True) + ip_mock.assert_has_calls([ mock.call(namespace=router_ns), mock.call().netns.execute(netns_execute_args, addl_env=None, run_as_root=False) ]) - def test_spawn_metadata_proxy_with_agent_user(self): - self._test_spawn_metadata_proxy( - self.EUNAME, str(self.EGID), user=self.EUNAME) + def test_create_config_file_wrong_user(self): + with mock.patch('pwd.getpwnam', side_effect=KeyError): + config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY, + mock.ANY, mock.ANY, + self.EUNAME, + self.EGNAME, + mock.ANY, mock.ANY) + self.assertRaises(metadata_driver.InvalidUserOrGroupException, + config.create_config_file) - def test_spawn_metadata_proxy_with_nonagent_user(self): - self._test_spawn_metadata_proxy( - 'notneutron', str(self.EGID), user='notneutron', watch_log=False) + def test_create_config_file_wrong_group(self): + with mock.patch('grp.getgrnam', side_effect=KeyError),\ + mock.patch('pwd.getpwnam', + return_value=test_utils.FakeUser(self.EUNAME)): + config = metadata_driver.HaproxyConfigurator(mock.ANY, mock.ANY, + mock.ANY, mock.ANY, + self.EUNAME, + self.EGNAME, + mock.ANY, mock.ANY) + self.assertRaises(metadata_driver.InvalidUserOrGroupException, + config.create_config_file) - def test_spawn_metadata_proxy_with_agent_uid(self): - self._test_spawn_metadata_proxy( - str(self.EUID), str(self.EGID), user=str(self.EUID)) + def test__migrate_python_ns_metadata_proxy_if_needed(self): + agent = l3_agent.L3NATAgent('localhost') + with mock.patch( + 'neutron.agent.linux.external_process.ProcessManager')\ + as mock_pm: + mock_pm.cmdline = ( + 'python neutron-ns-metadata-proxy') + (agent.metadata_driver + ._migrate_python_ns_metadata_proxy_if_needed(mock_pm)) + mock_pm.disable.assert_called_once_with() - def test_spawn_metadata_proxy_with_nonagent_uid(self): - self._test_spawn_metadata_proxy( - '321', str(self.EGID), user='321', watch_log=False) - - def test_spawn_metadata_proxy_with_group(self): - self._test_spawn_metadata_proxy(str(self.EUID), 'group', group='group') - - def test_spawn_metadata_proxy_with_gid(self): - self._test_spawn_metadata_proxy(str(self.EUID), '654', group='654') - - def test_spawn_metadata_proxy(self): - self._test_spawn_metadata_proxy(str(self.EUID), str(self.EGID)) + def test__migrate_python_ns_metadata_proxy_if_needed_not_called(self): + agent = l3_agent.L3NATAgent('localhost') + with mock.patch( + 'neutron.agent.linux.external_process.ProcessManager')\ + as mock_pm: + mock_pm.cmdline = ( + 'haproxy -f foo.cfg') + (agent.metadata_driver + ._migrate_python_ns_metadata_proxy_if_needed(mock_pm)) + mock_pm.disable.assert_not_called() diff --git a/neutron/tests/unit/agent/metadata/test_namespace_proxy.py b/neutron/tests/unit/agent/metadata/test_namespace_proxy.py deleted file mode 100644 index fda4e02ca32..00000000000 --- a/neutron/tests/unit/agent/metadata/test_namespace_proxy.py +++ /dev/null @@ -1,313 +0,0 @@ -# Copyright 2012 New Dream Network, LLC (DreamHost) -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import mock -import testtools -import webob - -from neutron.agent.linux import utils as agent_utils -from neutron.agent.metadata import namespace_proxy as ns_proxy -from neutron.common import exceptions -from neutron.common import utils -from neutron.tests import base -from neutron import wsgi - - -class TestNetworkMetadataProxyHandler(base.BaseTestCase): - def setUp(self): - super(TestNetworkMetadataProxyHandler, self).setUp() - self.handler = ns_proxy.NetworkMetadataProxyHandler('router_id') - - def test_call(self): - req = mock.Mock(headers={}) - with mock.patch.object(self.handler, '_proxy_request') as proxy_req: - proxy_req.return_value = 'value' - - retval = self.handler(req) - self.assertEqual(retval, 'value') - proxy_req.assert_called_once_with(req.remote_addr, - req.method, - req.path_info, - req.query_string, - req.body) - - def test_no_argument_passed_to_init(self): - with testtools.ExpectedException( - exceptions.NetworkIdOrRouterIdRequiredError): - ns_proxy.NetworkMetadataProxyHandler() - - def test_call_internal_server_error(self): - req = mock.Mock(headers={}) - with mock.patch.object(self.handler, '_proxy_request') as proxy_req: - proxy_req.side_effect = Exception - retval = self.handler(req) - self.assertIsInstance(retval, webob.exc.HTTPInternalServerError) - self.assertTrue(proxy_req.called) - - def test_proxy_request_router_200(self): - self.handler.router_id = 'router_id' - - resp = mock.MagicMock(status=200) - with mock.patch('httplib2.Http') as mock_http: - resp.__getitem__.return_value = "text/plain" - mock_http.return_value.request.return_value = (resp, 'content') - - retval = self.handler._proxy_request('192.168.1.1', - 'GET', - '/latest/meta-data', - '', - '') - - mock_http.assert_has_calls([ - mock.call().request( - 'http://169.254.169.254/latest/meta-data', - method='GET', - headers={ - 'X-Forwarded-For': '192.168.1.1', - 'X-Neutron-Router-ID': 'router_id' - }, - connection_type=agent_utils.UnixDomainHTTPConnection, - body='' - )] - ) - - self.assertEqual(retval.headers['Content-Type'], 'text/plain') - self.assertEqual(b'content', retval.body) - - def _test_proxy_request_network_200(self, content): - self.handler.network_id = 'network_id' - - resp = mock.MagicMock(status=200) - with mock.patch('httplib2.Http') as mock_http: - resp.__getitem__.return_value = "application/json" - mock_http.return_value.request.return_value = (resp, content) - - retval = self.handler._proxy_request('192.168.1.1', - 'GET', - '/latest/meta-data', - '', - '') - - mock_http.assert_has_calls([ - mock.call().request( - 'http://169.254.169.254/latest/meta-data', - method='GET', - headers={ - 'X-Forwarded-For': '192.168.1.1', - 'X-Neutron-Network-ID': 'network_id' - }, - connection_type=agent_utils.UnixDomainHTTPConnection, - body='' - )] - ) - - self.assertEqual(retval.headers['Content-Type'], - 'application/json') - self.assertEqual(wsgi.encode_body(content), retval.body) - - def test_proxy_request_network_200(self): - self._test_proxy_request_network_200('{}') - - def test_proxy_request_network_200_unicode_in_content(self): - self._test_proxy_request_network_200('Gl\xfcck') - - def _test_proxy_request_network_4xx(self, status, method, expected): - self.handler.network_id = 'network_id' - - resp = mock.Mock(status=status) - with mock.patch('httplib2.Http') as mock_http: - mock_http.return_value.request.return_value = (resp, '') - - retval = self.handler._proxy_request('192.168.1.1', - method, - '/latest/meta-data', - '', - '') - - mock_http.assert_has_calls([ - mock.call().request( - 'http://169.254.169.254/latest/meta-data', - method=method, - headers={ - 'X-Forwarded-For': '192.168.1.1', - 'X-Neutron-Network-ID': 'network_id' - }, - connection_type=agent_utils.UnixDomainHTTPConnection, - body='' - )] - ) - - self.assertIsInstance(retval, expected) - - def test_proxy_request_network_400(self): - self._test_proxy_request_network_4xx( - 400, 'GET', webob.exc.HTTPBadRequest) - - def test_proxy_request_network_404(self): - self._test_proxy_request_network_4xx( - 404, 'GET', webob.exc.HTTPNotFound) - - def test_proxy_request_network_409(self): - self._test_proxy_request_network_4xx( - 409, 'POST', webob.exc.HTTPConflict) - - def test_proxy_request_network_500(self): - self.handler.network_id = 'network_id' - - resp = mock.Mock(status=500) - with mock.patch('httplib2.Http') as mock_http: - mock_http.return_value.request.return_value = (resp, '') - - retval = self.handler._proxy_request('192.168.1.1', - 'GET', - '/latest/meta-data', - '', - '') - - mock_http.assert_has_calls([ - mock.call().request( - 'http://169.254.169.254/latest/meta-data', - method='GET', - headers={ - 'X-Forwarded-For': '192.168.1.1', - 'X-Neutron-Network-ID': 'network_id' - }, - connection_type=agent_utils.UnixDomainHTTPConnection, - body='' - )] - ) - - self.assertIsInstance(retval, webob.exc.HTTPInternalServerError) - - def test_proxy_request_network_418(self): - self.handler.network_id = 'network_id' - - resp = mock.Mock(status=418) - with mock.patch('httplib2.Http') as mock_http: - mock_http.return_value.request.return_value = (resp, '') - - with testtools.ExpectedException(Exception): - self.handler._proxy_request('192.168.1.1', - 'GET', - '/latest/meta-data', - '', - '') - - mock_http.assert_has_calls([ - mock.call().request( - 'http://169.254.169.254/latest/meta-data', - method='GET', - headers={ - 'X-Forwarded-For': '192.168.1.1', - 'X-Neutron-Network-ID': 'network_id' - }, - connection_type=agent_utils.UnixDomainHTTPConnection, - body='' - )] - ) - - def test_proxy_request_network_exception(self): - self.handler.network_id = 'network_id' - - mock.Mock(status=500) - with mock.patch('httplib2.Http') as mock_http: - mock_http.return_value.request.side_effect = Exception - - with testtools.ExpectedException(Exception): - self.handler._proxy_request('192.168.1.1', - 'GET', - '/latest/meta-data', - '', - '') - - mock_http.assert_has_calls([ - mock.call().request( - 'http://169.254.169.254/latest/meta-data', - method='GET', - headers={ - 'X-Forwarded-For': '192.168.1.1', - 'X-Neutron-Network-ID': 'network_id' - }, - connection_type=agent_utils.UnixDomainHTTPConnection, - body='' - )] - ) - - -class TestProxyDaemon(base.BaseTestCase): - def test_init(self): - with mock.patch('neutron.agent.linux.daemon.Pidfile'): - pd = ns_proxy.ProxyDaemon('pidfile', 9697, 'net_id', 'router_id') - self.assertEqual(pd.router_id, 'router_id') - self.assertEqual(pd.network_id, 'net_id') - - def test_run(self): - with mock.patch('neutron.agent.linux.daemon.Pidfile'): - with mock.patch('neutron.wsgi.Server') as Server: - pd = ns_proxy.ProxyDaemon('pidfile', 9697, 'net_id', - 'router_id') - pd.run() - Server.assert_has_calls([ - mock.call('neutron-network-metadata-proxy'), - mock.call().start(mock.ANY, 9697), - mock.call().wait()] - ) - - def test_main(self): - with mock.patch.object(ns_proxy, 'ProxyDaemon') as daemon: - with mock.patch.object(ns_proxy, 'config') as config: - with mock.patch.object(ns_proxy, 'cfg') as cfg: - with mock.patch.object(utils, 'cfg') as utils_cfg: - cfg.CONF.router_id = 'router_id' - cfg.CONF.network_id = None - cfg.CONF.metadata_port = 9697 - cfg.CONF.pid_file = 'pidfile' - cfg.CONF.daemonize = True - utils_cfg.CONF.log_opt_values.return_value = None - ns_proxy.main() - - self.assertTrue(config.setup_logging.called) - daemon.assert_has_calls([ - mock.call('pidfile', 9697, - router_id='router_id', - network_id=None, - user=mock.ANY, - group=mock.ANY, - watch_log=mock.ANY), - mock.call().start()] - ) - - def test_main_dont_fork(self): - with mock.patch.object(ns_proxy, 'ProxyDaemon') as daemon: - with mock.patch.object(ns_proxy, 'config') as config: - with mock.patch.object(ns_proxy, 'cfg') as cfg: - with mock.patch.object(utils, 'cfg') as utils_cfg: - cfg.CONF.router_id = 'router_id' - cfg.CONF.network_id = None - cfg.CONF.metadata_port = 9697 - cfg.CONF.pid_file = 'pidfile' - cfg.CONF.daemonize = False - utils_cfg.CONF.log_opt_values.return_value = None - ns_proxy.main() - - self.assertTrue(config.setup_logging.called) - daemon.assert_has_calls([ - mock.call('pidfile', 9697, - router_id='router_id', - network_id=None, - user=mock.ANY, - group=mock.ANY, - watch_log=mock.ANY), - mock.call().run()] - ) diff --git a/releasenotes/notes/switching-to-haproxy-for-metadata-proxy-9d8f7549fadf9182.yaml b/releasenotes/notes/switching-to-haproxy-for-metadata-proxy-9d8f7549fadf9182.yaml new file mode 100644 index 00000000000..dbc8aded631 --- /dev/null +++ b/releasenotes/notes/switching-to-haproxy-for-metadata-proxy-9d8f7549fadf9182.yaml @@ -0,0 +1,12 @@ +--- +features: + - In order to reduce metadata proxy memory footprint, ``haproxy`` is now used + as a replacement for ``neutron-ns-metadata-proxy`` Python implementation. +upgrade: + - Since ``haproxy`` was not used before by ``neutron-l3-agent`` and + ``neutron-dhcp-agent``, rootwrap filters for both agents have to be copied + over when upgrading. + - To upgrade to the ``haproxy`` based metadata proxy, ``neutron-l3-agent`` + and ``neutron-dhcp-agent`` have to be restarted. On startup, old proxy + processes will be detected and replaced with ``haproxy``. + diff --git a/setup.cfg b/setup.cfg index e6c2fdab9b2..cfa10e8947a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,7 +53,6 @@ console_scripts = neutron-macvtap-agent = neutron.cmd.eventlet.plugins.macvtap_neutron_agent:main neutron-metadata-agent = neutron.cmd.eventlet.agents.metadata:main neutron-netns-cleanup = neutron.cmd.netns_cleanup:main - neutron-ns-metadata-proxy = neutron.cmd.eventlet.agents.metadata_proxy:main neutron-openvswitch-agent = neutron.cmd.eventlet.plugins.ovs_neutron_agent:main neutron-ovs-cleanup = neutron.cmd.ovs_cleanup:main neutron-pd-notify = neutron.cmd.pd_notify:main