From ebc7e1fb2f264bc91c9ad0f43e7e0208eede7a25 Mon Sep 17 00:00:00 2001 From: tonytan4ever Date: Thu, 1 Sep 2016 23:40:49 -0400 Subject: [PATCH] Pecan: Get loaded by paste deploy This sets up the factory methods needed to have paste deploy create the pecan app and return it. It also changes the legacy factory methods to conditionally use the pecan factory methods if the web_framework config option is set to 'pecan'. This way, all deployments of neutron will not need to change their api-paste.ini files to get pecan toggled on. It should just happen without notice once pecan becomes the default. Also, by moving this to be loaded by paste deploy, there is a good chunk of code that has been removed because it is no longer necessary. Co-Authored-By: Brandon Logan Change-Id: I8b1bbea8d90fdc62715cd8b6738ad955df53d7cd --- neutron/api/v2/router.py | 3 + neutron/api/versions.py | 4 + neutron/cmd/eventlet/server/__init__.py | 12 +-- neutron/pecan_wsgi/app.py | 80 +++---------------- neutron/pecan_wsgi/controllers/extensions.py | 8 +- neutron/pecan_wsgi/controllers/root.py | 28 ++++--- neutron/pecan_wsgi/hooks/context.py | 32 +------- .../pecan_wsgi/hooks/policy_enforcement.py | 9 ++- neutron/pecan_wsgi/startup.py | 13 ++- neutron/server/wsgi_pecan.py | 28 ------- neutron/tests/etc/api-paste.ini | 45 +++++++++++ .../functional/pecan_wsgi/test_controllers.py | 22 ++--- .../functional/pecan_wsgi/test_functional.py | 60 +++++++++++--- neutron/tests/unit/api/test_versions.py | 34 ++++++++ .../__init__.py => api/v2/test_router.py} | 20 ++--- 15 files changed, 209 insertions(+), 189 deletions(-) delete mode 100644 neutron/server/wsgi_pecan.py create mode 100644 neutron/tests/etc/api-paste.ini create mode 100644 neutron/tests/unit/api/test_versions.py rename neutron/tests/unit/{cmd/server/__init__.py => api/v2/test_router.py} (63%) diff --git a/neutron/api/v2/router.py b/neutron/api/v2/router.py index baa36a8a84c..64516a02b4d 100644 --- a/neutron/api/v2/router.py +++ b/neutron/api/v2/router.py @@ -28,6 +28,7 @@ from neutron.api import extensions from neutron.api.v2 import attributes from neutron.api.v2 import base from neutron import manager +from neutron.pecan_wsgi import app as pecan_app from neutron import policy from neutron.quota import resource_registry from neutron import wsgi @@ -70,6 +71,8 @@ class APIRouter(base_wsgi.Router): @classmethod def factory(cls, global_config, **local_config): + if cfg.CONF.web_framework == 'pecan': + return pecan_app.v2_factory(global_config, **local_config) return cls(**local_config) def __init__(self, **local_config): diff --git a/neutron/api/versions.py b/neutron/api/versions.py index 52d452c68df..5c85f9c6cdf 100644 --- a/neutron/api/versions.py +++ b/neutron/api/versions.py @@ -13,11 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg import oslo_i18n import webob.dec from neutron._i18n import _ from neutron.api.views import versions as versions_view +from neutron.pecan_wsgi import app as pecan_app from neutron import wsgi @@ -25,6 +27,8 @@ class Versions(object): @classmethod def factory(cls, global_config, **local_config): + if cfg.CONF.web_framework == 'pecan': + return pecan_app.versions_factory(global_config, **local_config) return cls(app=None) @webob.dec.wsgify(RequestClass=wsgi.Request) diff --git a/neutron/cmd/eventlet/server/__init__.py b/neutron/cmd/eventlet/server/__init__.py index a0cef174d49..32425fa6c85 100644 --- a/neutron/cmd/eventlet/server/__init__.py +++ b/neutron/cmd/eventlet/server/__init__.py @@ -10,23 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_config import cfg - from neutron import server from neutron.server import rpc_eventlet from neutron.server import wsgi_eventlet -from neutron.server import wsgi_pecan def main(): - server.boot_server(_main_neutron_server) - - -def _main_neutron_server(): - if cfg.CONF.web_framework == 'legacy': - wsgi_eventlet.eventlet_wsgi_server() - else: - wsgi_pecan.pecan_wsgi_server() + server.boot_server(wsgi_eventlet.eventlet_wsgi_server) def main_rpc_eventlet(): diff --git a/neutron/pecan_wsgi/app.py b/neutron/pecan_wsgi/app.py index ff548a776f4..ffad7be7b61 100644 --- a/neutron/pecan_wsgi/app.py +++ b/neutron/pecan_wsgi/app.py @@ -13,37 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. -from keystonemiddleware import auth_token -from neutron_lib import exceptions as n_exc -from oslo_config import cfg -from oslo_middleware import cors -from oslo_middleware import http_proxy_to_wsgi -from oslo_middleware import request_id import pecan -from neutron.api import versions +from neutron.pecan_wsgi.controllers import root from neutron.pecan_wsgi import hooks from neutron.pecan_wsgi import startup -CONF = cfg.CONF -CONF.import_opt('bind_host', 'neutron.conf.common') -CONF.import_opt('bind_port', 'neutron.conf.common') + +def versions_factory(global_config, **local_config): + return pecan.make_app(root.RootController()) -def setup_app(*args, **kwargs): - config = { - 'server': { - 'port': CONF.bind_port, - 'host': CONF.bind_host - }, - 'app': { - 'root': 'neutron.pecan_wsgi.controllers.root.RootController', - 'modules': ['neutron.pecan_wsgi'], - } - #TODO(kevinbenton): error templates - } - pecan_config = pecan.configuration.conf_from_dict(config) - +def v2_factory(global_config, **local_config): app_hooks = [ hooks.ExceptionTranslationHook(), # priority 100 hooks.ContextHook(), # priority 95 @@ -54,51 +35,10 @@ def setup_app(*args, **kwargs): hooks.QueryParametersHook(), # priority 139 hooks.PolicyHook(), # priority 140 ] - - app = pecan.make_app( - pecan_config.app.root, - debug=False, - wrap_app=_wrap_app, - force_canonical=False, - hooks=app_hooks, - guess_content_type_from_ext=True - ) + app = pecan.make_app(root.V2Controller(), + debug=False, + force_canonical=False, + hooks=app_hooks, + guess_content_type_from_ext=True) startup.initialize_all() - - return app - - -def _wrap_app(app): - app = request_id.RequestId(app) - if cfg.CONF.auth_strategy == 'noauth': - pass - elif cfg.CONF.auth_strategy == 'keystone': - app = auth_token.AuthProtocol(app, {}) - else: - raise n_exc.InvalidConfigurationOption( - opt_name='auth_strategy', opt_value=cfg.CONF.auth_strategy) - - # version can be unauthenticated so it goes outside of auth - app = versions.Versions(app) - - # handle cases where neutron-server is behind a proxy - app = http_proxy_to_wsgi.HTTPProxyToWSGI(app) - - # This should be the last middleware in the list (which results in - # it being the first in the middleware chain). This is to ensure - # that any errors thrown by other middleware, such as an auth - # middleware - are annotated with CORS headers, and thus accessible - # by the browser. - app = cors.CORS(app, cfg.CONF) - cors.set_defaults( - allow_headers=['X-Auth-Token', 'X-Identity-Status', 'X-Roles', - 'X-Service-Catalog', 'X-User-Id', 'X-Tenant-Id', - 'X-OpenStack-Request-ID', - 'X-Trace-Info', 'X-Trace-HMAC'], - allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'], - expose_headers=['X-Auth-Token', 'X-Subject-Token', 'X-Service-Token', - 'X-OpenStack-Request-ID', - 'X-Trace-Info', 'X-Trace-HMAC'] - ) - return app diff --git a/neutron/pecan_wsgi/controllers/extensions.py b/neutron/pecan_wsgi/controllers/extensions.py index 251b856df1d..e95b751d4c3 100644 --- a/neutron/pecan_wsgi/controllers/extensions.py +++ b/neutron/pecan_wsgi/controllers/extensions.py @@ -38,7 +38,9 @@ class ExtensionsController(object): @utils.when(index, method='HEAD') @utils.when(index, method='PATCH') def not_supported(self): - pecan.abort(405) + # NOTE(blogan): Normally we'd return 405 but the legacy extensions + # controller returned 404. + pecan.abort(404) class ExtensionController(object): @@ -62,4 +64,6 @@ class ExtensionController(object): @utils.when(index, method='HEAD') @utils.when(index, method='PATCH') def not_supported(self): - pecan.abort(405) + # NOTE(blogan): Normally we'd return 405 but the legacy extensions + # controller returned 404. + pecan.abort(404) diff --git a/neutron/pecan_wsgi/controllers/root.py b/neutron/pecan_wsgi/controllers/root.py index f1d87e3d7dd..5793f12c467 100644 --- a/neutron/pecan_wsgi/controllers/root.py +++ b/neutron/pecan_wsgi/controllers/root.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg from oslo_log import log import pecan from pecan import request @@ -24,6 +25,9 @@ from neutron import manager from neutron.pecan_wsgi.controllers import extensions as ext_ctrl from neutron.pecan_wsgi.controllers import utils + +CONF = cfg.CONF + LOG = log.getLogger(__name__) _VERSION_INFO = {} @@ -41,12 +45,16 @@ class RootController(object): @utils.expose(generic=True) def index(self): - # NOTE(kevinbenton): The pecan framework does not handle - # any requests to the root because they are intercepted - # by the 'version' returning wrapper. - pass + version_objs = [ + { + "id": "v2.0", + "status": "CURRENT", + }, + ] + builder = versions_view.get_view_builder(pecan.request) + versions = [builder.build(version) for version in version_objs] + return dict(versions=versions) - @utils.when(index, method='GET') @utils.when(index, method='HEAD') @utils.when(index, method='POST') @utils.when(index, method='PATCH') @@ -66,6 +74,11 @@ class V2Controller(object): } _load_version_info(version_info) + # NOTE(blogan): Paste deploy handled the routing to the legacy extension + # controller. If the extensions filter is removed from the api-paste.ini + # then this controller will be routed to This means operators had + # the ability to turn off the extensions controller via tha api-paste but + # will not be able to turn it off with the pecan switch. extensions = ext_ctrl.ExtensionsController() @utils.expose(generic=True) @@ -112,8 +125,3 @@ class V2Controller(object): # with the uri_identifiers request.context['uri_identifiers'] = {} return controller, remainder - - -# This controller cannot be specified directly as a member of RootController -# as its path is not a valid python identifier -pecan.route(RootController, 'v2.0', V2Controller()) diff --git a/neutron/pecan_wsgi/hooks/context.py b/neutron/pecan_wsgi/hooks/context.py index ae38f6393ad..552f8fb886e 100644 --- a/neutron/pecan_wsgi/hooks/context.py +++ b/neutron/pecan_wsgi/hooks/context.py @@ -13,41 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_middleware import request_id from pecan import hooks -from neutron import context - class ContextHook(hooks.PecanHook): - """Configures a request context and attaches it to the request. - The following HTTP request headers are used: - X-User-Id or X-User: - Used for context.user_id. - X-Project-Id: - Used for context.tenant_id. - X-Project-Name: - Used for context.tenant_name. - X-Auth-Token: - Used for context.auth_token. - X-Roles: - Used for setting context.is_admin flag to either True or False. - The flag is set to True, if X-Roles contains either an administrator - or admin substring. Otherwise it is set to False. - """ - + """Moves the request env's neutron.context into the requests context.""" priority = 95 def before(self, state): - user_name = state.request.headers.get('X-User-Name', '') - tenant_name = state.request.headers.get('X-Project-Name') - req_id = state.request.headers.get(request_id.ENV_REQUEST_ID) - # TODO(kevinbenton): is_admin logic - # Create a context with the authentication data - ctx = context.Context.from_environ(state.request.environ, - user_name=user_name, - tenant_name=tenant_name, - request_id=req_id) - - # Inject the context... + ctx = state.request.environ['neutron.context'] state.request.context['neutron_context'] = ctx diff --git a/neutron/pecan_wsgi/hooks/policy_enforcement.py b/neutron/pecan_wsgi/hooks/policy_enforcement.py index df1f741254c..0af23dd0989 100644 --- a/neutron/pecan_wsgi/hooks/policy_enforcement.py +++ b/neutron/pecan_wsgi/hooks/policy_enforcement.py @@ -183,11 +183,14 @@ class PolicyHook(hooks.PecanHook): policy_method(neutron_context, action, item, plugin=plugin, pluralized=collection))] - except oslo_policy.PolicyNotAuthorized as e: + except oslo_policy.PolicyNotAuthorized: # This exception must be explicitly caught as the exception # translation hook won't be called if an error occurs in the - # 'after' handler. - raise webob.exc.HTTPForbidden(str(e)) + # 'after' handler. Instead of raising an HTTPForbidden exception, + # we have to set the status_code here to prevent the catch_errors + # middleware from turning this into a 500. + state.response.status_code = 403 + return if is_single: resp = resp[0] diff --git a/neutron/pecan_wsgi/startup.py b/neutron/pecan_wsgi/startup.py index 9c021760eae..0b442990456 100644 --- a/neutron/pecan_wsgi/startup.py +++ b/neutron/pecan_wsgi/startup.py @@ -18,7 +18,6 @@ from neutron_lib.plugins import directory from neutron.api import extensions from neutron.api.v2 import attributes from neutron.api.v2 import base -from neutron.api.v2 import router from neutron import manager from neutron.pecan_wsgi.controllers import resource as res_ctrl from neutron.pecan_wsgi.controllers import utils @@ -26,6 +25,16 @@ from neutron import policy from neutron.quota import resource_registry +# NOTE(blogan): This currently already exists in neutron.api.v2.router but +# instead of importing that module and creating circular imports elsewhere, +# it's easier to just copy it here. The likelihood of it needing to be changed +# are slim to none. +RESOURCES = {'network': 'networks', + 'subnet': 'subnets', + 'subnetpool': 'subnetpools', + 'port': 'ports'} + + def initialize_all(): manager.init() ext_mgr = extensions.PluginAwareExtensionManager.get_instance() @@ -33,7 +42,7 @@ def initialize_all(): # At this stage we have a fully populated resource attribute map; # build Pecan controllers and routes for all core resources plugin = directory.get_plugin() - for resource, collection in router.RESOURCES.items(): + for resource, collection in RESOURCES.items(): resource_registry.register_resource_by_name(resource) new_controller = res_ctrl.CollectionsController(collection, resource, plugin=plugin) diff --git a/neutron/server/wsgi_pecan.py b/neutron/server/wsgi_pecan.py deleted file mode 100644 index 6f43ea8fe02..00000000000 --- a/neutron/server/wsgi_pecan.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/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. - -from oslo_log import log - -from neutron._i18n import _LI -from neutron.pecan_wsgi import app as pecan_app -from neutron.server import wsgi_eventlet -from neutron import service - -LOG = log.getLogger(__name__) - - -def pecan_wsgi_server(): - LOG.info(_LI("Pecan WSGI server starting...")) - application = pecan_app.setup_app() - neutron_api = service.run_wsgi_app(application) - wsgi_eventlet.start_api_and_rpc_workers(neutron_api) diff --git a/neutron/tests/etc/api-paste.ini b/neutron/tests/etc/api-paste.ini new file mode 100644 index 00000000000..1c98cfe3676 --- /dev/null +++ b/neutron/tests/etc/api-paste.ini @@ -0,0 +1,45 @@ +[composite:neutron] +use = egg:Paste#urlmap +/: neutronversions_composite +/v2.0: neutronapi_v2_0 + +[composite:neutronapi_v2_0] +use = call:neutron.auth:pipeline_factory +noauth = cors http_proxy_to_wsgi request_id catch_errors extensions neutronapiapp_v2_0 +keystone = cors http_proxy_to_wsgi request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0 + +[composite:neutronversions_composite] +use = call:neutron.auth:pipeline_factory +noauth = cors http_proxy_to_wsgi neutronversions +keystone = cors http_proxy_to_wsgi neutronversions + +[filter:request_id] +paste.filter_factory = oslo_middleware:RequestId.factory + +[filter:catch_errors] +paste.filter_factory = oslo_middleware:CatchErrors.factory + +[filter:cors] +paste.filter_factory = oslo_middleware.cors:filter_factory +oslo_config_project = neutron + +[filter:http_proxy_to_wsgi] +paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.factory + +[filter:keystonecontext] +paste.filter_factory = neutron.auth:NeutronKeystoneContext.factory + +[filter:authtoken] +paste.filter_factory = keystonemiddleware.auth_token:filter_factory + +[filter:extensions] +paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory + +[app:neutronversions] +paste.app_factory = neutron.api.versions:Versions.factory + +[app:neutronapiapp_v2_0] +paste.app_factory = neutron.api.v2.router:APIRouter.factory + +[filter:osprofiler] +paste.filter_factory = osprofiler.web:WsgiMiddleware.factory diff --git a/neutron/tests/functional/pecan_wsgi/test_controllers.py b/neutron/tests/functional/pecan_wsgi/test_controllers.py index edd482ee976..0187a8b1a42 100644 --- a/neutron/tests/functional/pecan_wsgi/test_controllers.py +++ b/neutron/tests/functional/pecan_wsgi/test_controllers.py @@ -81,11 +81,11 @@ class TestRootController(test_functional.PecanFunctionalTest): self.assertEqual(value, versions[0][attr]) def test_methods(self): - self._test_method_returns_code('post') - self._test_method_returns_code('patch') - self._test_method_returns_code('delete') - self._test_method_returns_code('head') - self._test_method_returns_code('put') + self._test_method_returns_code('post', 405) + self._test_method_returns_code('patch', 405) + self._test_method_returns_code('delete', 405) + self._test_method_returns_code('head', 405) + self._test_method_returns_code('put', 405) class TestV2Controller(TestRootController): @@ -146,12 +146,12 @@ class TestExtensionsController(TestRootController): self.assertEqual(test_alias, json_body['extension']['alias']) def test_methods(self): - self._test_method_returns_code('post', 405) - self._test_method_returns_code('put', 405) - self._test_method_returns_code('patch', 405) - self._test_method_returns_code('delete', 405) - self._test_method_returns_code('head', 405) - self._test_method_returns_code('delete', 405) + self._test_method_returns_code('post', 404) + self._test_method_returns_code('put', 404) + self._test_method_returns_code('patch', 404) + self._test_method_returns_code('delete', 404) + self._test_method_returns_code('head', 404) + self._test_method_returns_code('delete', 404) class TestQuotasController(test_functional.PecanFunctionalTest): diff --git a/neutron/tests/functional/pecan_wsgi/test_functional.py b/neutron/tests/functional/pecan_wsgi/test_functional.py index fd1ad4ef1fe..bc74ee9d5e6 100644 --- a/neutron/tests/functional/pecan_wsgi/test_functional.py +++ b/neutron/tests/functional/pecan_wsgi/test_functional.py @@ -19,23 +19,60 @@ import mock from neutron_lib import constants from neutron_lib import exceptions as n_exc from oslo_config import cfg +from oslo_middleware import base +from oslo_service import wsgi from oslo_utils import uuidutils -from pecan import set_config -from pecan.testing import load_test_app import testtools +import webob.dec +import webtest from neutron.api import extensions as exts +from neutron import context from neutron import manager +from neutron import tests from neutron.tests.unit import testlib_api +class InjectContext(base.ConfigurableMiddleware): + + @webob.dec.wsgify + def __call__(self, req): + user_id = req.headers.get('X_USER_ID', '') + + # Determine the tenant + tenant_id = req.headers.get('X_PROJECT_ID') + + # Suck out the roles + roles = [r.strip() for r in req.headers.get('X_ROLES', '').split(',')] + + # Human-friendly names + tenant_name = req.headers.get('X_PROJECT_NAME') + user_name = req.headers.get('X_USER_NAME') + + # Create a context with the authentication data + ctx = context.Context(user_id, tenant_id, roles=roles, + user_name=user_name, tenant_name=tenant_name) + req.environ['neutron.context'] = ctx + return self.application + + +def create_test_app(): + paste_config_loc = os.path.join(os.path.dirname(tests.__file__), 'etc', + 'api-paste.ini') + paste_config_loc = os.path.abspath(paste_config_loc) + cfg.CONF.set_override('api_paste_config', paste_config_loc) + loader = wsgi.Loader(cfg.CONF) + app = loader.load_app('neutron') + app = InjectContext(app) + return webtest.TestApp(app) + + class PecanFunctionalTest(testlib_api.SqlTestCase): def setUp(self, service_plugins=None, extensions=None): self.setup_coreplugin('ml2', load_plugins=False) super(PecanFunctionalTest, self).setUp() self.addCleanup(exts.PluginAwareExtensionManager.clear_instance) - self.addCleanup(set_config, {}, overwrite=True) self.set_config_overrides() manager.init() ext_mgr = exts.PluginAwareExtensionManager.get_instance() @@ -45,15 +82,10 @@ class PecanFunctionalTest(testlib_api.SqlTestCase): service_plugins[constants.CORE] = ext_mgr.plugins.get( constants.CORE) ext_mgr.plugins = service_plugins - self.setup_app() - - def setup_app(self): - self.app = load_test_app(os.path.join( - os.path.dirname(__file__), - 'config.py' - )) + self.app = create_test_app() def set_config_overrides(self): + cfg.CONF.set_override('web_framework', 'pecan') cfg.CONF.set_override('auth_strategy', 'noauth') def do_request(self, url, tenant_id=None, admin=False, @@ -109,8 +141,12 @@ class TestInvalidAuth(PecanFunctionalTest): def test_invalid_auth_strategy(self): cfg.CONF.set_override('auth_strategy', 'badvalue') - with testtools.ExpectedException(n_exc.InvalidConfigurationOption): - load_test_app(os.path.join(os.path.dirname(__file__), 'config.py')) + # NOTE(blogan): the auth.pipeline_factory will throw a KeyError + # with a bad value because that value is not the paste config. + # This KeyError is translated to a LookupError, which the oslo wsgi + # code translates into PasteAppNotFound. + with testtools.ExpectedException(wsgi.PasteAppNotFound): + create_test_app() class TestExceptionTranslationHook(PecanFunctionalTest): diff --git a/neutron/tests/unit/api/test_versions.py b/neutron/tests/unit/api/test_versions.py new file mode 100644 index 00000000000..edccbfceac9 --- /dev/null +++ b/neutron/tests/unit/api/test_versions.py @@ -0,0 +1,34 @@ +# 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 +from oslo_config import cfg + +from neutron.api import versions +from neutron.tests import base + + +@mock.patch('neutron.api.versions.Versions.__init__', return_value=None) +@mock.patch('neutron.pecan_wsgi.app.versions_factory') +class TestVersions(base.BaseTestCase): + + def test_legacy_factory(self, pecan_mock, legacy_mock): + cfg.CONF.set_override('web_framework', 'legacy') + versions.Versions.factory({}) + pecan_mock.assert_not_called() + legacy_mock.assert_called_once_with(app=None) + + def test_pecan_factory(self, pecan_mock, legacy_mock): + cfg.CONF.set_override('web_framework', 'pecan') + versions.Versions.factory({}) + pecan_mock.assert_called_once_with({}) + legacy_mock.assert_not_called() diff --git a/neutron/tests/unit/cmd/server/__init__.py b/neutron/tests/unit/api/v2/test_router.py similarity index 63% rename from neutron/tests/unit/cmd/server/__init__.py rename to neutron/tests/unit/api/v2/test_router.py index 45f87fceec1..33cecc3ae05 100644 --- a/neutron/tests/unit/cmd/server/__init__.py +++ b/neutron/tests/unit/api/v2/test_router.py @@ -13,22 +13,22 @@ import mock from oslo_config import cfg -from neutron.cmd.eventlet import server +from neutron.api.v2 import router from neutron.tests import base -@mock.patch('neutron.server.wsgi_eventlet.eventlet_wsgi_server') -@mock.patch('neutron.server.wsgi_pecan.pecan_wsgi_server') -class TestNeutronServer(base.BaseTestCase): +@mock.patch('neutron.api.v2.router.APIRouter.__init__', return_value=None) +@mock.patch('neutron.pecan_wsgi.app.v2_factory') +class TestRouter(base.BaseTestCase): - def test_legacy_server(self, pecan_mock, legacy_mock): + def test_legacy_factory(self, pecan_mock, legacy_mock): cfg.CONF.set_override('web_framework', 'legacy') - server._main_neutron_server() + router.APIRouter.factory({}) pecan_mock.assert_not_called() - legacy_mock.assert_called_with() + legacy_mock.assert_called_once_with() - def test_pecan_server(self, pecan_mock, legacy_mock): + def test_pecan_factory(self, pecan_mock, legacy_mock): cfg.CONF.set_override('web_framework', 'pecan') - server._main_neutron_server() - pecan_mock.assert_called_with() + router.APIRouter.factory({}) + pecan_mock.assert_called_once_with({}) legacy_mock.assert_not_called()