From 2ba2456f9dae2a4cf30804a562c08832c24b6231 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 24 Jul 2015 04:32:56 -0700 Subject: [PATCH] Pecan WSGI: prevent plugins from opening AMQP connections The Pecan WSGI server is supposed to server REST requests only. As several plugins initialize their RPC interfaces upon initialization, this results in establishing undesired connections to the AMQP server. This patch does not alter plugin behavior or initialization process, but ensures that the setup_rpc operation performed by a plugin has no effect when executed in the Pecan WSGI server. This patch also performs some refactoring for server launchers. Change-Id: I56384f5f964ea90d72babf911aa5639989e9c3d8 --- neutron/cmd/eventlet/server/__init__.py | 12 +++-- neutron/common/rpc.py | 25 +++++++++++ neutron/server/__init__.py | 28 ++---------- neutron/server/wsgi_eventlet.py | 47 +++++++++++++++++++ neutron/server/wsgi_pecan.py | 60 +++++++++++++++++++++++++ setup.cfg | 4 +- 6 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 neutron/server/wsgi_eventlet.py create mode 100755 neutron/server/wsgi_pecan.py diff --git a/neutron/cmd/eventlet/server/__init__.py b/neutron/cmd/eventlet/server/__init__.py index bfa2568628d..01c3b52c1ec 100644 --- a/neutron/cmd/eventlet/server/__init__.py +++ b/neutron/cmd/eventlet/server/__init__.py @@ -10,8 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron import server +from neutron.server import wsgi_eventlet +from neutron.server import wsgi_pecan -def main(): - server.main() +def main_wsgi_eventlet(): + # This also starts the RPC server + wsgi_eventlet.main() + + +def main_wsgi_pecan(): + wsgi_pecan.main() diff --git a/neutron/common/rpc.py b/neutron/common/rpc.py index 6fe39842b7e..3037f5342f0 100644 --- a/neutron/common/rpc.py +++ b/neutron/common/rpc.py @@ -47,6 +47,12 @@ TRANSPORT_ALIASES = { 'neutron.rpc.impl_zmq': 'zmq', } +# NOTE(salv-orlando): I am afraid this is a global variable. While not ideal, +# they're however widely used throughout the code base. It should be set to +# true if the RPC server is not running in the current process space. This +# will prevent get_connection from creating connections to the AMQP server +RPC_DISABLED = False + def init(conf): global TRANSPORT, NOTIFIER @@ -201,6 +207,25 @@ class Connection(object): server.wait() +class VoidConnection(object): + + def create_consumer(self, topic, endpoints, fanout=False): + pass + + def consume_in_threads(self): + pass + + def close(self): + pass + + # functions def create_connection(new=True): + # NOTE(salv-orlando): This is a clever interpreation of the factory design + # patter aimed at preventing plugins from initializing RPC servers upon + # initialization when they are running in the REST over HTTP API server. + # The educated reader will perfectly be able that this a fairly dirty hack + # to avoid having to change the initialization process of every plugin. + if RPC_DISABLED: + return VoidConnection() return Connection() diff --git a/neutron/server/__init__.py b/neutron/server/__init__.py index 6c5bd4fe600..108e9f4d4ed 100644 --- a/neutron/server/__init__.py +++ b/neutron/server/__init__.py @@ -20,43 +20,21 @@ import sys -import eventlet from oslo_config import cfg -from oslo_log import log as logging from neutron.common import config -from neutron.i18n import _LI -from neutron import service - -LOG = logging.getLogger(__name__) -def main(): +def boot_server(server_func): # the configuration will be read into the cfg.CONF global data structure config.init(sys.argv[1:]) + config.setup_logging() if not cfg.CONF.config_file: sys.exit(_("ERROR: Unable to find configuration file via the default" " search paths (~/.neutron/, ~/, /etc/neutron/, /etc/) and" " the '--config-file' option!")) try: - pool = eventlet.GreenPool() - - neutron_api = service.serve_wsgi(service.NeutronApiService) - api_thread = pool.spawn(neutron_api.wait) - - try: - neutron_rpc = service.serve_rpc() - except NotImplementedError: - LOG.info(_LI("RPC was already started in parent process by " - "plugin.")) - else: - rpc_thread = pool.spawn(neutron_rpc.wait) - - # api and rpc should die together. When one dies, kill the other. - rpc_thread.link(lambda gt: api_thread.kill()) - api_thread.link(lambda gt: rpc_thread.kill()) - - pool.waitall() + server_func() except KeyboardInterrupt: pass except RuntimeError as e: diff --git a/neutron/server/wsgi_eventlet.py b/neutron/server/wsgi_eventlet.py new file mode 100644 index 00000000000..ad89f3ea48a --- /dev/null +++ b/neutron/server/wsgi_eventlet.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# 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 eventlet + +from oslo_log import log + +from neutron.i18n import _LI +from neutron import server +from neutron import service + +LOG = log.getLogger(__name__) + + +def _eventlet_wsgi_server(): + pool = eventlet.GreenPool() + + neutron_api = service.serve_wsgi(service.NeutronApiService) + api_thread = pool.spawn(neutron_api.wait) + + try: + neutron_rpc = service.serve_rpc() + except NotImplementedError: + LOG.info(_LI("RPC was already started in parent process by " + "plugin.")) + else: + rpc_thread = pool.spawn(neutron_rpc.wait) + + # api and rpc should die together. When one dies, kill the other. + rpc_thread.link(lambda gt: api_thread.kill()) + api_thread.link(lambda gt: rpc_thread.kill()) + + pool.waitall() + + +def main(): + server.boot_server(_eventlet_wsgi_server) diff --git a/neutron/server/wsgi_pecan.py b/neutron/server/wsgi_pecan.py new file mode 100755 index 00000000000..a9d62b69628 --- /dev/null +++ b/neutron/server/wsgi_pecan.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# 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 logging as std_logging +from wsgiref import simple_server + +from oslo_config import cfg +from oslo_log import log +from six.moves import socketserver + +from neutron.common import rpc as n_rpc +from neutron.i18n import _LI, _LW +from neutron.newapi import app as pecan_app +from neutron import server + +LOG = log.getLogger(__name__) + + +class ThreadedSimpleServer(socketserver.ThreadingMixIn, + simple_server.WSGIServer): + pass + + +def _pecan_wsgi_server(): + LOG.info(_LI("Pecan WSGI server starting...")) + # No AMQP connection should be created within this process + n_rpc.RPC_DISABLED = True + application = pecan_app.setup_app() + + host = cfg.CONF.bind_host + port = cfg.CONF.bind_port + + wsgi = simple_server.make_server( + host, + port, + application, + server_class=ThreadedSimpleServer + ) + # Log option values + cfg.CONF.log_opt_values(LOG, std_logging.DEBUG) + LOG.warning( + _LW("Development Server Serving on http://%(host)s:%(port)s"), + {'host': host, 'port': port} + ) + + wsgi.serve_forever() + + +def main(): + server.boot_server(_pecan_wsgi_server) diff --git a/setup.cfg b/setup.cfg index 8ecc3ce6c13..f1bcddaf9f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -86,7 +86,6 @@ scripts = console_scripts = neutron-db-manage = neutron.db.migration.cli:main neutron-debug = neutron.debug.shell:main - neutron-dev-server = neutron.cmd.eventlet.api:main neutron-dhcp-agent = neutron.cmd.eventlet.agents.dhcp:main neutron-hyperv-agent = neutron.cmd.eventlet.plugins.hyperv_neutron_agent:main neutron-keepalived-state-change = neutron.cmd.keepalived_state_change:main @@ -104,7 +103,8 @@ console_scripts = neutron-openvswitch-agent = neutron.cmd.eventlet.plugins.ovs_neutron_agent:main neutron-ovs-cleanup = neutron.cmd.ovs_cleanup:main neutron-restproxy-agent = neutron.plugins.bigswitch.agent.restproxy_agent:main - neutron-server = neutron.cmd.eventlet.server:main + neutron-server = neutron.cmd.eventlet.server:main_wsgi_eventlet + neutron-dev-server = neutron.cmd.eventlet.server:main_wsgi_pecan neutron-rootwrap = oslo_rootwrap.cmd:main neutron-rootwrap-daemon = oslo_rootwrap.cmd:daemon neutron-usage-audit = neutron.cmd.usage_audit:main