Load wsgi apps with paste.deploy
This change replaces the hard coded WSGI app creation with a pipeline of WSGI apps declared in a configuration file. Change-Id: I1969e3eec7be1c28b637451c71cfe450c91b959f Partially-Implements: blueprint support-multi-tenancy
This commit is contained in:
parent
e91ecb39bc
commit
7b6f108821
@ -0,0 +1,16 @@
|
|||||||
|
[pipeline:main]
|
||||||
|
pipeline = cors request_id authtoken api_v1
|
||||||
|
|
||||||
|
[app:api_v1]
|
||||||
|
paste.app_factory = zun.api.app:app_factory
|
||||||
|
|
||||||
|
[filter:authtoken]
|
||||||
|
acl_public_routes = /, /v1
|
||||||
|
paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory
|
||||||
|
|
||||||
|
[filter:request_id]
|
||||||
|
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||||
|
|
||||||
|
[filter:cors]
|
||||||
|
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||||
|
oslo_config_project = zun
|
@ -14,6 +14,7 @@ oslo.log>=1.14.0 # Apache-2.0
|
|||||||
oslo.concurrency>=3.8.0 # Apache-2.0
|
oslo.concurrency>=3.8.0 # Apache-2.0
|
||||||
oslo.config>=3.14.0 # Apache-2.0
|
oslo.config>=3.14.0 # Apache-2.0
|
||||||
oslo.messaging>=5.2.0 # Apache-2.0
|
oslo.messaging>=5.2.0 # Apache-2.0
|
||||||
|
oslo.middleware>=3.0.0 # Apache-2.0
|
||||||
oslo.policy>=1.9.0 # Apache-2.0
|
oslo.policy>=1.9.0 # Apache-2.0
|
||||||
oslo.serialization>=1.10.0 # Apache-2.0
|
oslo.serialization>=1.10.0 # Apache-2.0
|
||||||
oslo.service>=1.10.0 # Apache-2.0
|
oslo.service>=1.10.0 # Apache-2.0
|
||||||
|
@ -54,6 +54,9 @@ console_scripts =
|
|||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
zun = zun.opts:list_opts
|
zun = zun.opts:list_opts
|
||||||
|
|
||||||
|
oslo.config.opts.defaults =
|
||||||
|
zun = zun.common.config.set_cors_middleware_defaults
|
||||||
|
|
||||||
zun.database.migration_backend =
|
zun.database.migration_backend =
|
||||||
sqlalchemy = zun.db.sqlalchemy.migration
|
sqlalchemy = zun.db.sqlalchemy.migration
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ namespace = oslo.concurrency
|
|||||||
namespace = oslo.db
|
namespace = oslo.db
|
||||||
namespace = oslo.log
|
namespace = oslo.log
|
||||||
namespace = oslo.messaging
|
namespace = oslo.messaging
|
||||||
|
namespace = oslo.middleware.cors
|
||||||
namespace = oslo.policy
|
namespace = oslo.policy
|
||||||
namespace = oslo.service.periodic_task
|
namespace = oslo.service.periodic_task
|
||||||
namespace = oslo.service.service
|
namespace = oslo.service.service
|
||||||
|
@ -10,13 +10,18 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
from paste import deploy
|
||||||
import pecan
|
import pecan
|
||||||
|
|
||||||
from zun.api import config as api_config
|
from zun.api import config as api_config
|
||||||
from zun.api import middleware
|
from zun.api import middleware
|
||||||
|
from zun.common import config as common_config
|
||||||
from zun.common.i18n import _
|
from zun.common.i18n import _
|
||||||
|
from zun.common.i18n import _LI
|
||||||
|
|
||||||
|
|
||||||
# Register options for the service
|
# Register options for the service
|
||||||
@ -41,7 +46,10 @@ API_SERVICE_OPTS = [
|
|||||||
cfg.IntOpt('max_limit',
|
cfg.IntOpt('max_limit',
|
||||||
default=1000,
|
default=1000,
|
||||||
help='The maximum number of items returned in a single '
|
help='The maximum number of items returned in a single '
|
||||||
'response from a collection resource.')
|
'response from a collection resource.'),
|
||||||
|
cfg.StrOpt('api_paste_config',
|
||||||
|
default="api-paste.ini",
|
||||||
|
help="Configuration file for WSGI definition of API.")
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
@ -64,6 +72,7 @@ def setup_app(config=None):
|
|||||||
config = get_pecan_config()
|
config = get_pecan_config()
|
||||||
|
|
||||||
app_conf = dict(config.app)
|
app_conf = dict(config.app)
|
||||||
|
common_config.set_config_defaults()
|
||||||
|
|
||||||
app = pecan.make_app(
|
app = pecan.make_app(
|
||||||
app_conf.pop('root'),
|
app_conf.pop('root'),
|
||||||
@ -73,3 +82,21 @@ def setup_app(config=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def load_app():
|
||||||
|
cfg_file = None
|
||||||
|
cfg_path = cfg.CONF.api.api_paste_config
|
||||||
|
if not os.path.isabs(cfg_path):
|
||||||
|
cfg_file = CONF.find_file(cfg_path)
|
||||||
|
elif os.path.exists(cfg_path):
|
||||||
|
cfg_file = cfg_path
|
||||||
|
|
||||||
|
if not cfg_file:
|
||||||
|
raise cfg.ConfigFilesNotFoundError([cfg.CONF.api.api_paste_config])
|
||||||
|
LOG.info(_LI("Full WSGI config used: %s"), cfg_file)
|
||||||
|
return deploy.loadapp("config:" + cfg_file)
|
||||||
|
|
||||||
|
|
||||||
|
def app_factory(global_config, **local_conf):
|
||||||
|
return setup_app()
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_middleware import cors
|
||||||
|
|
||||||
from zun.common import rpc
|
from zun.common import rpc
|
||||||
from zun import version
|
from zun import version
|
||||||
@ -28,3 +29,31 @@ def parse_args(argv, default_config_files=None):
|
|||||||
version=version.version_info.release_string(),
|
version=version.version_info.release_string(),
|
||||||
default_config_files=default_config_files)
|
default_config_files=default_config_files)
|
||||||
rpc.init(cfg.CONF)
|
rpc.init(cfg.CONF)
|
||||||
|
|
||||||
|
|
||||||
|
def set_config_defaults():
|
||||||
|
"""This method updates all configuration default values."""
|
||||||
|
set_cors_middleware_defaults()
|
||||||
|
|
||||||
|
|
||||||
|
def set_cors_middleware_defaults():
|
||||||
|
"""Update default configuration options for oslo.middleware."""
|
||||||
|
cfg.set_defaults(cors.CORS_OPTS,
|
||||||
|
allow_headers=['X-Auth-Token',
|
||||||
|
'X-Identity-Status',
|
||||||
|
'X-Roles',
|
||||||
|
'X-Service-Catalog',
|
||||||
|
'X-User-Id',
|
||||||
|
'X-Project-Id',
|
||||||
|
'X-OpenStack-Request-ID',
|
||||||
|
'X-Server-Management-Url'],
|
||||||
|
expose_headers=['X-Auth-Token',
|
||||||
|
'X-Subject-Token',
|
||||||
|
'X-Service-Token',
|
||||||
|
'X-OpenStack-Request-ID',
|
||||||
|
'X-Server-Management-Url'],
|
||||||
|
allow_methods=['GET',
|
||||||
|
'PUT',
|
||||||
|
'POST',
|
||||||
|
'DELETE',
|
||||||
|
'PATCH'])
|
||||||
|
@ -49,6 +49,7 @@ def prepare_service(argv=None):
|
|||||||
argv = []
|
argv = []
|
||||||
log.register_options(CONF)
|
log.register_options(CONF)
|
||||||
config.parse_args(argv)
|
config.parse_args(argv)
|
||||||
|
config.set_config_defaults()
|
||||||
log.setup(CONF, 'zun')
|
log.setup(CONF, 'zun')
|
||||||
# TODO(yuanying): Uncomment after objects are implemented
|
# TODO(yuanying): Uncomment after objects are implemented
|
||||||
# objects.register_all()
|
# objects.register_all()
|
||||||
@ -69,7 +70,7 @@ class WSGIService(service.ServiceBase):
|
|||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
self.app = app.setup_app()
|
self.app = app.load_app()
|
||||||
self.workers = (CONF.api.workers or processutils.get_worker_count())
|
self.workers = (CONF.api.workers or processutils.get_worker_count())
|
||||||
if self.workers and self.workers < 1:
|
if self.workers and self.workers < 1:
|
||||||
raise exception.ConfigInvalid(
|
raise exception.ConfigInvalid(
|
||||||
|
18
zun/tests/unit/api/controllers/auth-paste.ini
Normal file
18
zun/tests/unit/api/controllers/auth-paste.ini
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[pipeline:main]
|
||||||
|
pipeline = cors request_id authtoken api_v1
|
||||||
|
|
||||||
|
[app:api_v1]
|
||||||
|
paste.app_factory = zun.api.app:app_factory
|
||||||
|
|
||||||
|
[filter:authtoken]
|
||||||
|
paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory
|
||||||
|
|
||||||
|
[filter:request_id]
|
||||||
|
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||||
|
|
||||||
|
[filter:cors]
|
||||||
|
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||||
|
oslo_config_project = zun
|
||||||
|
latent_allow_methods = GET, PUT, POST, DELETE
|
||||||
|
latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID
|
||||||
|
latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID
|
19
zun/tests/unit/api/controllers/auth-root-access.ini
Normal file
19
zun/tests/unit/api/controllers/auth-root-access.ini
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[pipeline:main]
|
||||||
|
pipeline = cors request_id authtoken api_v1
|
||||||
|
|
||||||
|
[app:api_v1]
|
||||||
|
paste.app_factory = zun.api.app:app_factory
|
||||||
|
|
||||||
|
[filter:authtoken]
|
||||||
|
acl_public_routes = /
|
||||||
|
paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory
|
||||||
|
|
||||||
|
[filter:request_id]
|
||||||
|
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||||
|
|
||||||
|
[filter:cors]
|
||||||
|
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||||
|
oslo_config_project = zun
|
||||||
|
latent_allow_methods = GET, PUT, POST, DELETE
|
||||||
|
latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID
|
||||||
|
latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID
|
19
zun/tests/unit/api/controllers/auth-v1-access.ini
Normal file
19
zun/tests/unit/api/controllers/auth-v1-access.ini
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[pipeline:main]
|
||||||
|
pipeline = cors request_id authtoken api_v1
|
||||||
|
|
||||||
|
[app:api_v1]
|
||||||
|
paste.app_factory = zun.api.app:app_factory
|
||||||
|
|
||||||
|
[filter:authtoken]
|
||||||
|
acl_public_routes = /v1
|
||||||
|
paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory
|
||||||
|
|
||||||
|
[filter:request_id]
|
||||||
|
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||||
|
|
||||||
|
[filter:cors]
|
||||||
|
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||||
|
oslo_config_project = zun
|
||||||
|
latent_allow_methods = GET, PUT, POST, DELETE
|
||||||
|
latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID
|
||||||
|
latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID
|
19
zun/tests/unit/api/controllers/noauth-paste.ini
Normal file
19
zun/tests/unit/api/controllers/noauth-paste.ini
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[pipeline:main]
|
||||||
|
pipeline = cors request_id api_v1
|
||||||
|
|
||||||
|
[app:api_v1]
|
||||||
|
paste.app_factory = zun.api.app:app_factory
|
||||||
|
|
||||||
|
[filter:authtoken]
|
||||||
|
acl_public_routes = /
|
||||||
|
paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory
|
||||||
|
|
||||||
|
[filter:request_id]
|
||||||
|
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||||
|
|
||||||
|
[filter:cors]
|
||||||
|
paste.filter_factory = oslo_middleware.cors:filter_factory
|
||||||
|
oslo_config_project = zun
|
||||||
|
latent_allow_methods = GET, PUT, POST, DELETE
|
||||||
|
latent_allow_headers = X-Auth-Token, X-Identity-Status, X-Roles, X-Service-Catalog, X-User-Id, X-Tenant-Id, X-OpenStack-Request-ID
|
||||||
|
latent_expose_headers = X-Auth-Token, X-Subject-Token, X-Service-Token, X-OpenStack-Request-ID
|
123
zun/tests/unit/api/controllers/test_root.py
Normal file
123
zun/tests/unit/api/controllers/test_root.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
import webtest
|
||||||
|
|
||||||
|
from zun.api import app
|
||||||
|
from zun.tests.unit.api import base as api_base
|
||||||
|
|
||||||
|
|
||||||
|
class TestRootController(api_base.FunctionalTest):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestRootController, self).setUp()
|
||||||
|
self.root_expected = {
|
||||||
|
u'default_version':
|
||||||
|
{u'id': u'v1', u'links':
|
||||||
|
[{u'href': u'http://localhost/v1/', u'rel': u'self'}]},
|
||||||
|
u'description': u'Zun is an OpenStack project which '
|
||||||
|
'aims to provide container management.',
|
||||||
|
u'versions': [{u'id': u'v1',
|
||||||
|
u'links':
|
||||||
|
[{u'href': u'http://localhost/v1/',
|
||||||
|
u'rel': u'self'}]}]}
|
||||||
|
|
||||||
|
self.v1_expected = {
|
||||||
|
u'media_types':
|
||||||
|
[{u'base': u'application/json',
|
||||||
|
u'type': u'application/vnd.openstack.zun.v1+json'}],
|
||||||
|
u'links': [{u'href': u'http://localhost/v1/',
|
||||||
|
u'rel': u'self'},
|
||||||
|
{u'href':
|
||||||
|
u'http://docs.openstack.org/developer'
|
||||||
|
'/zun/dev/api-spec-v1.html',
|
||||||
|
u'type': u'text/html', u'rel': u'describedby'}],
|
||||||
|
u'services': [{u'href': u'http://localhost/v1/services/',
|
||||||
|
u'rel': u'self'},
|
||||||
|
{u'href': u'http://localhost/services/',
|
||||||
|
u'rel': u'bookmark'}],
|
||||||
|
u'id': u'v1',
|
||||||
|
u'containers': [{u'href': u'http://localhost/v1/containers/',
|
||||||
|
u'rel': u'self'},
|
||||||
|
{u'href': u'http://localhost/containers/',
|
||||||
|
u'rel': u'bookmark'}]}
|
||||||
|
|
||||||
|
def make_app(self, paste_file):
|
||||||
|
file_name = self.get_path(paste_file)
|
||||||
|
cfg.CONF.set_override("api_paste_config", file_name, group="api")
|
||||||
|
return webtest.TestApp(app.load_app())
|
||||||
|
|
||||||
|
def test_version(self):
|
||||||
|
response = self.app.get('/')
|
||||||
|
self.assertEqual(self.root_expected, response.json)
|
||||||
|
|
||||||
|
def test_v1_controller(self):
|
||||||
|
response = self.app.get('/v1/')
|
||||||
|
self.assertEqual(self.v1_expected, response.json)
|
||||||
|
|
||||||
|
def test_get_not_found(self):
|
||||||
|
response = self.app.get('/a/bogus/url', expect_errors=True)
|
||||||
|
assert response.status_int == 404
|
||||||
|
|
||||||
|
def test_noauth(self):
|
||||||
|
# Don't need to auth
|
||||||
|
paste_file = "zun/tests/unit/api/controllers/noauth-paste.ini"
|
||||||
|
app = self.make_app(paste_file)
|
||||||
|
|
||||||
|
response = app.get('/')
|
||||||
|
self.assertEqual(self.root_expected, response.json)
|
||||||
|
|
||||||
|
response = app.get('/v1/')
|
||||||
|
self.assertEqual(self.v1_expected, response.json)
|
||||||
|
|
||||||
|
response = app.get('/v1/containers/')
|
||||||
|
self.assertEqual(200, response.status_int)
|
||||||
|
|
||||||
|
def test_auth_with_no_public_routes(self):
|
||||||
|
# All apis need auth when access
|
||||||
|
paste_file = "zun/tests/unit/api/controllers/auth-paste.ini"
|
||||||
|
app = self.make_app(paste_file)
|
||||||
|
|
||||||
|
response = app.get('/', expect_errors=True)
|
||||||
|
self.assertEqual(401, response.status_int)
|
||||||
|
|
||||||
|
response = app.get('/v1/', expect_errors=True)
|
||||||
|
self.assertEqual(401, response.status_int)
|
||||||
|
|
||||||
|
def test_auth_with_root_access(self):
|
||||||
|
# Only / can access without auth
|
||||||
|
paste_file = "zun/tests/unit/api/controllers/auth-root-access.ini"
|
||||||
|
app = self.make_app(paste_file)
|
||||||
|
|
||||||
|
response = app.get('/')
|
||||||
|
self.assertEqual(self.root_expected, response.json)
|
||||||
|
|
||||||
|
response = app.get('/v1/', expect_errors=True)
|
||||||
|
self.assertEqual(401, response.status_int)
|
||||||
|
|
||||||
|
response = app.get('/v1/containers', expect_errors=True)
|
||||||
|
self.assertEqual(401, response.status_int)
|
||||||
|
|
||||||
|
def test_auth_with_v1_access(self):
|
||||||
|
# Only /v1 can access without auth
|
||||||
|
paste_file = "zun/tests/unit/api/controllers/auth-v1-access.ini"
|
||||||
|
app = self.make_app(paste_file)
|
||||||
|
|
||||||
|
response = app.get('/', expect_errors=True)
|
||||||
|
self.assertEqual(401, response.status_int)
|
||||||
|
|
||||||
|
response = app.get('/v1/')
|
||||||
|
self.assertEqual(self.v1_expected, response.json)
|
||||||
|
|
||||||
|
response = app.get('/v1/containers', expect_errors=True)
|
||||||
|
self.assertEqual(401, response.status_int)
|
Loading…
Reference in New Issue
Block a user